XMLHttpRequest.upload.onprogress可捕获上传进度,需在send前绑定,仅对POST/PUT有效;event.total是否可靠取决于服务端CORS配置(Access-Control-Expose-Headers: Content-Length)及iOS版本兼容性(iOS 14.5+支持)。

用 XMLHttpRequest.upload.onprogress 捕获上传进度
HTML5 本身不提供“上传状态”组件,必须靠 XMLHttpRequest 的上传事件手动监听。核心是绑定 upload.onprogress,它会在每次数据块发送后触发,传入 ProgressEvent 对象。
注意:这个事件只对 POST(或 PUT)请求有效,且必须在调用 send() 前绑定;如果用 fetch,目前没有等价的原生上传进度钩子(需借助 ReadableStream + 分块上传模拟,复杂度高,不推荐初用)。
-
event.loaded表示已上传字节数,event.total是总字节数(仅当服务端返回Content-Length或设置withCredentials: false时可靠) - 若
event.lengthComputable === false,说明服务端未返回明确长度,只能显示“正在上传…”而非百分比 - 避免高频更新 DOM:建议用
requestAnimationFrame节流,或每 500ms 更新一次,否则滚动卡顿
FormData 上传时如何正确触发进度事件
用 FormData 封装文件后,仍需通过 XMLHttpRequest 发送,不能直接丢给 fetch 或表单 submit —— 后两者不暴露底层上传流。
常见错误是把 input[type="file"] 的 files[0] 直接赋给 FormData.append() 后就不管了,其实还要确保服务端接收逻辑支持二进制流(比如 Node.js 的 multer、PHP 的 $_FILES),否则 event.total 可能为 0。
立即学习“前端免费学习笔记(深入)”;
- 必须显式设置
xhr.open('POST', url),不能依赖表单默认提交 -
FormData实例可直接传给xhr.send(form),无需手动序列化 - 若上传多个文件,
event.total是所有文件总大小,不是单个文件大小
const input = document.querySelector('input[type="file"]');
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
console.log(上传中:${percent}%);
} else {
console.log('上传中…(大小未知)');
}
};
xhr.onload = function () {
if (xhr.status === 200) {
console.log('上传完成');
}
};
input.addEventListener('change', () => {
const file = input.files[0];
const form = new FormData();
form.append('file', file);
xhr.open('POST', '/upload');
xhr.send(form);
});
服务端响应头影响 event.total 是否可用
XMLHttpRequest.upload.onprogress 能否拿到准确 event.total 值,取决于服务端是否在响应中设置了 Access-Control-Allow-Headers: Content-Length 和实际返回了 Content-Length 响应头 —— 但更关键的是:服务端是否在预检(OPTIONS)响应中允许该头。
典型现象:本地开发时 event.total 正常,部署到 Nginx 后变成 0。原因常是 Nginx 默认不透传 Content-Length 到前端,或 CORS 配置遗漏 Access-Control-Expose-Headers: Content-Length。
- Node.js + Express 示例:需加
res.set('Access-Control-Expose-Headers', 'Content-Length') - Nginx 配置片段:
add_header 'Access-Control-Expose-Headers' 'Content-Length'; - 即使服务端没返回
Content-Length,event.loaded依然有效,可用于估算(如结合文件 size 属性)
移动端 Safari 对 onprogress 的兼容性陷阱
iOS 14.5+ 开始支持 XMLHttpRequest.upload.onprogress,但旧版 Safari(iOS 13 及更早)完全不触发该事件 —— 不会报错,也不会回调,容易误判为“上传卡住”。
没有优雅降级方案,只能检测环境并提示用户:“请升级浏览器”或改用分片上传 + 服务端轮询(如查 /upload/status?id=xxx)。
- 检测方式:
'onprogress' in xhr.upload,但 iOS 13 返回 true 却不执行回调,属 WebKit bug - 真实项目中建议加超时兜底:
xhr.timeout = 30000,并监听ontimeout - 不要依赖
onloadstart或onloadend推算进度,它们只表示请求生命周期,不反映上传过程
实际做上传状态时,最易被忽略的是服务端 CORS 头配置和 iOS 旧版本静默失效问题。这两个点不提前验证,上线后用户看到的就是“一直转圈”,且无法定位原因。











