必须用 xmlhttprequest 手动发请求并监听 upload.onprogress,因 fetch 不支持上传进度、form.submit() 无返回对象且默认提交会跳转,直接给 form 绑定 onprogress 无效;需 preventdefault、用 formdata、监听 xhr.upload.onprogress 而非 xhr.onprogress,并做 lengthcomputable 防御判断。

必须用 XMLHttpRequest 手动发请求并监听 upload.onprogress,fetch 目前不支持上传进度事件,原生表单提交更无法暴露上传过程。
为什么不能用 form.submit() 或 onsubmit 绑定 progress?
表单默认提交会整页跳转或刷新,onprogress 事件根本不会触发;form.submit() 是同步方法,不返回可监听的请求对象。所有试图给 <form></form> 元素直接加 onprogress 属性或监听其 submit 事件来更新进度的做法,全部无效。
常见错误现象包括:progress.value 始终为 0、控制台无任何 onprogress 日志、上传完成才突然跳转到响应页。
- 必须调用
event.preventDefault()阻止默认提交行为 - 必须用
new FormData(form)收集字段(含文件),而不是序列化成 JSON 或拼 query string - 必须用
xhr.upload.onprogress,不是xhr.onprogress(后者是下载事件)
XMLHttpRequest 上传进度监听的关键写法
upload.onprogress 在 IE10+、Chrome、Firefox、Safari(最新稳定版)中可用,但行为有差异:某些代理或 Nginx 配置下 e.lengthComputable 为 false,此时 e.total === 0,直接除会导致 NaN。
正确写法要包含防御性判断:
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
progress.value = Math.min(100, Math.round(percent));
}
};
xhr.onload = function() {
if (xhr.status === 200) {
progress.value = 100; // 强制补满,防四舍五入卡在 99%
}
};
xhr.send(formData);
- 不要手动设置
Content-Type头——FormData发送时浏览器自动加带 boundary 的multipart/form-data,设错会导致后端收不到文件(如 PHP 的$_FILES为空) - 额外参数(如 token)应通过
formData.append('token', 'xxx')添加,而非塞进 headers - 上传按钮应在
xhr.send()前disabled = true,防止重复点击
进度条 UI 更新容易被忽略的细节
<progress max="100"></progress> 的 max 属性必须写死为 100,不要靠 JS 动态设 max;否则 Safari 会出现渲染异常或值重置。
更新 value 时若不节流,在高速网络或小文件场景下可能高频触发,造成 UI 卡顿或数值抖动。
- 推荐用
requestAnimationFrame包一层更新逻辑,避免连续重绘 - 上传完成时即使
e.loaded === e.total已触发过一次 100%,仍要在xhr.onload中再设一次progress.value = 100 - 服务端返回 HTTP 200 不代表业务成功——需解析响应体,检查
responseJSON.success === true等字段,不能只看状态码
真正难的不是让进度动起来,而是确保 FormData 构建方式与后端 multipart 解析完全对齐,以及在各种边界网络条件下(如 Nginx client_max_body_size 截断、代理吞掉 Content-Length)仍能给出准确反馈。这些地方出问题,进度条可能“跑得飞快”却实际没传出去。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











