进度条不动主因是监听对象错误或时机不当:必须用xhr.upload.onprogress,且须在xhr.open()后、xhr.send()前绑定;后端缺失Content-Length响应头会导致e.lengthComputable为false。

进度条不动,90% 是因为绑错了监听对象或时机不对——必须用 xhr.upload.onprogress,且只能在 xhr.open() 之后、xhr.send() 之前设置。
为什么 onprogress 死活不触发
不是代码写得不够多,而是两个硬性条件没满足:
-
xhr.onprogress是下载事件,上传必须写成xhr.upload.onprogress - 监听绑定必须发生在
xhr.open()调用之后、xhr.send()调用之前;写在open前或send后都无效 - 后端响应头缺失
Content-Length(比如被 Nginx 截断、代理转发丢弃、或用了某些压缩中间件),会导致e.lengthComputable === false,e.total为 0,进度无法计算 - Safari 对小文件(如 upload.onprogress 触发极稀疏,可能只报一次,这不是 bug,是它的流式策略
验证方式:用 curl -I https://your-api/upload 检查响应头是否含 Content-Length。
用 <progress> 还是自定义 <div>
<progress> 语义清晰、代码少,但样式控制困难:
立即学习“前端免费学习笔记(深入)”;
- 各浏览器伪元素名不同:
::-webkit-progress-bar、::-moz-progress-bar、::-ms-fill,没法一套 CSS 通吃 - 必须动态改
value属性:progressEl.value = percent;只改style.width完全没反应 - 若需文字居中、失败态提示、动画缓动,
<div class="progress-bar"><div class="progress"></div></div>更可控 - 无论哪种,都建议加节流:高频
onprogress下,用requestAnimationFrame或时间戳限制更新频率(例如每 100ms 最多一次)
FormData 构建常见翻车点
进度条动了 ≠ 文件真传过去了。后端收不到,大概率卡在 FormData 写法上:
- 字段名必须和服务端约定一致:
formData.append('file', file)中的'file'要和后端接收字段名完全匹配(比如 Express + multer 配置的name字段) - 别手动设
Content-Type请求头——XMLHttpRequest会自动加multipart/form-data; boundary=xxx,设错直接导致后端解析失败 -
append()和set()行为不同:重复 key 时,append()留多个值,set()只留最后一个;Express multer 默认取第一个,PHP$_FILES也只认第一个 - 要传 JSON 数据 + 文件,别
append('data', JSON.stringify(obj)),应拆成独立字段:formData.append('user_id', '123')、formData.append('meta', '{"type":"avatar"}'),或让后端支持嵌套 multipart 解析
后端配合的关键检查项
前端再准,后端一拦,进度就崩:
- Node.js + Express:禁用全局
body-parser(它会缓存整个请求体),改用multer或busboy流式接收 - PHP:确认
upload_max_filesize和post_max_size足够大,且未启用mod_security(它可能静默拦截 multipart 请求) - Python Flask:避免用
request.form或request.json提前读取请求体,要用request.stream.read()流式处理 - 所有后端:确保未开启请求体 gzip 压缩(干扰
Content-Length计算),跨域时返回Access-Control-Allow-Headers: X-Requested-With
真正卡住的地方往往不是前端逻辑,而是后端悄悄截断了 Content-Length,或者前端把 onprogress 绑在了 xhr 而不是 xhr.upload 上——这两个点,必须逐个排除。



















