input[type="file"]自身不提供进度信息,因其仅触发文件选择且不参与网络传输;可靠进度监听须用XMLHttpRequest手动绑定upload.onprogress事件,并以file.size计算百分比。

为什么 input[type="file"] 自身不提供进度信息
input[type="file"] 只负责触发文件选择、暴露 files 列表,它不参与网络传输,也没有 onprogress、upload 或任何与上传过程相关的属性或事件。表单默认提交(submit)会整页跳转,无法拦截、无法监听字节流。所谓“绑定表单的 progress 事件”是常见误解——HTML 表单原生不发射上传进度事件。
xhr.upload.onprogress 必须手动绑定且时机敏感
进度监听唯一可靠路径是显式创建 XMLHttpRequest 实例,并监听其 upload.onprogress 事件。注意不是 xhr.onprogress(那是下载),也不是 xhr.onreadystatechange(只管完成)。关键约束有:
- 必须在
xhr.open()之后、xhr.send()之前绑定xhr.upload.onprogress,否则事件注册失效 -
event.lengthComputable常为false,因为服务端未返回Content-Length响应头(尤其 Nginx、Express+multer 默认不设),此时event.total不可信,不能直接用于百分比计算 - 推荐用
file.size替代event.total:它稳定、可读、无需服务端配合,公式为Math.round((event.loaded / file.size) * 100) - 避免在
onprogress回调里高频更新 DOM(如每毫秒改progress.value),建议加简单阈值判断:仅当loaded相比上次变化 ≥ 1% 或 ≥ 64KB 时才更新 UI
FormData 构造和请求头别乱动
用 FormData 封装文件是最简方案,但容易踩两个坑:
- 不要手动设置
Content-Type头——xhr.send(formData)会自动添加multipart/form-data; boundary=...,手设会导致 boundary 缺失,后端解析失败 -
formData.append('file', file)即可,无需额外blob转换;若需传额外字段(如userId),统一append进去,别拼 query string - 如果后端要求 JSON(如 base64 字符串),则
total是该字符串长度,但注意大文件 base64 体积膨胀 33%,内存压力陡增,慎用
fetch 不能替代 xhr 实现原生进度监听
截至 2026 年 6 月,所有主流浏览器仍不支持 fetch 的上传进度钩子。试图混用 fetch 和 xhr(比如“fetch 发请求,xhr 监听”)不可行——它们走不同底层通道,无法共享上传实例。可行但高成本的替代路径只有:
立即学习“前端免费学习笔记(深入)”;
- 手动切片 + 分块
fetch:把文件切成 Blob,逐片上传并累加已传量,但后端必须支持分片协议,前端逻辑复杂度翻倍 - ReadableStream 流式上传(Chrome 115+ 实验性支持):需自行处理 boundary 拼接、背压控制、错误恢复,Safari/Firefox 不兼容,生产环境不推荐
真实项目里,只要需要稳定、可访问、跨浏览器的上传进度反馈,就坚持用 XMLHttpRequest + FormData + xhr.upload.onprogress。真正的进度必须绑定字节级上传过程,而不是按钮变灰或转圈动画——后者用户根本不知道卡在哪一环节。



















