必须用 xmlhttprequest.upload.onprogress 而非 xhr.onprogress 监听上传进度,因后者用于下载;需同时更新 style.width 和 aria-valuenow 以保障视觉与无障碍同步,并在 e.lengthcomputable 为 false 时切换至 indeterminate 状态。
不能直接用 bootstrap 的 btn 或 progress 组合成“带进度条的提交按钮”——它不是现成组件,必须手动组合 input[type="file"]、xmlhttprequest.upload.onprogress 和 bootstrap 进度条结构,并拦截默认提交行为。
为什么表单提交时进度条不动或只跳到 100%
核心问题在事件监听对象错了:你绑了 xhr.onprogress,但上传进度实际来自 xhr.upload.onprogress。浏览器原生 XMLHttpRequest 的 upload 对象才触发上传阶段的 ProgressEvent;xhr.onprogress 是下载用的,上传时基本不触发或只在结束时来一次。
- 必须在
xhr.open()之后、xhr.send()之前绑定xhr.upload.onprogress -
e.loaded和e.total都要判空:if (e.lengthComputable)才计算百分比,否则e.total === 0会导致NaN - 如果用
fetch,它根本不暴露上传进度——别硬套,老实用XMLHttpRequest
如何让进度条视觉和可访问性同步更新
Bootstrap 进度条靠两个东西驱动:内联 style.width 控制宽度,aria-valuenow 属性支持读屏器。只改一个,UI 或无障碍就断掉。
- 每次拿到新进度值,必须同时写:
bar.style.width = percent + '%'和bar.setAttribute('aria-valuenow', Math.round(percent)) - 容器要有
class="progress",子元素是class="progress-bar",缺一不可(Bootstrap 5 强制要求) - 别用
classList.replace()或$().css()动态改宽度——它们不响应width变化,得直操作style.width - 进度条需加
role="progressbar",否则屏幕阅读器读不出当前值
怎么把“上传按钮”和进度条真正串起来
Bootstrap 没有内置带进度的按钮,只能用 label 包裹隐藏的 input[type="file"],再通过 JS 触发上传并更新进度条。
- 把
input[type="file"]设为position: absolute; left: -9999px,用label[for]关联它 - 点击自定义按钮(比如
<button class="btn btn-primary">上传</button>)实际触发的是背后文件选择 - 监听
input的change事件拿到FileList,构造FormData后用XMLHttpRequest提交 - 提交前禁用按钮:
submitBtn.disabled = true;上传完成或失败后重置:submitBtn.disabled = false - 移动端注意:表单默认提交会刷新页面,必须在
form.onsubmit里event.preventDefault(),且不能写在监听函数外层
最易被忽略的是 e.total 为 0 的情况——尤其当 FormData 含文件时,浏览器不一定能准确算出总字节。这时候别卡死,该切到 indeterminate 状态:bar.classList.add("progress-bar-striped", "progress-bar-animated"),移除 aria-valuenow,等真实进度上来再切回 determinate。











