进度条不触发主因是监听对象错误或时机不当:应使用 xhr.upload.onprogress(非 xhr.onprogress),且监听须在 xhr.open() 后、xhr.send() 前设置;后端缺失 content-length 响应头也会导致 e.lengthcomputable 为 false。

HTML 的 <input type="file"> 本身不提供上传进度,必须用 JavaScript 监听 xhr.upload.onprogress 才能实时更新进度条——别试 fetch,它不暴露上传事件。
为什么 onprogress 死活不触发
90% 的“进度条不动”问题都卡在这两个地方:
- 把监听写成了
xhr.onprogress(这是下载事件),正确写法是xhr.upload.onprogress - 监听代码放在
xhr.open()之前,或xhr.send()之后——必须在中间这段区间内设置 - 后端没返回
Content-Length响应头,导致e.lengthComputable === false,e.total为0;可用curl -I https://your-api/upload检查响应头 - Safari 对小文件(比如 onprogress,不是 bug,是它的流式上传策略
<progress></progress> 和自定义 <div> 怎么选
<p><code><progress></progress> 语义正确、代码少,但样式难统一(各浏览器伪元素名不同,如 ::-webkit-progress-bar vs ::-moz-progress-bar);<div> 方案更可控,尤其要加动画、文字居中、失败态提示时。
<ul>
<li>用 <code><progress></progress> 时,必须动态改 value 属性:progressEl.value = percent,只改 style.width 没用
<div> 时,推荐加 CSS <code>transition: width 0.2s ease 避免跳变
onprogress 触发下,用 requestAnimationFrame 或简单计时器限制 UI 更新频率(比如每 100ms 最多更新一次)
FormData 构建时最容易踩的坑
进度条动了 ≠ 文件真传过去了。很多“上传成功但后端收不到”的问题,根源在 FormData 写法。
- 字段名必须和服务端约定一致:后端 expect
file,你却formData.append('myfile', file),就直接丢弃 - 别手动设
Content-Type请求头——XMLHttpRequest会自动加multipart/form-data; boundary=xxx,设错了会导致后端解析失败 -
append()和set()行为不同:重复 key 时,append()保留多个值,set()只留最后一个;取决于后端框架怎么处理(例如 Express 的multer默认只取第一个) - 同时传 JSON 和文件?别
append('data', JSON.stringify(obj)),应拆成独立字段,或让后端支持嵌套 multipart 解析
后端配合的关键点
前端再准,后端一拦,进度就崩。常见断点:
- Node.js + Express:避免
body-parser全局解析请求体(它会缓存整个上传流),改用multer或busboy流式接收 - PHP:确认
upload_max_filesize和post_max_size足够大,且未启用mod_security等拦截 multipart 的模块 - Python Flask:用
request.stream.read(chunk_size)分块读,别用request.files(它会等整个文件加载完才触发) - 所有后端:禁用请求体 gzip 压缩(Nginx 的
gzip_types application/json之类配置若误配到multipart,会干扰Content-Length计算)
真正卡住的地方,往往不是 onprogress 写错了,而是后端悄悄截断了 Content-Length,或者前端把监听绑在了 xhr 而不是 xhr.upload 上——这两个点,必须先盯死。











