用 setInterval 模拟进度条最直接,需控制数字从 0 增至 100 并绑定 DOM;requestAnimationFrame 更流畅但需时间戳计算;XMLHttpRequest 支持 onprogress 获取真实进度;AbortController 中断时须同步清理定时器或动画帧。

用 setInterval 模拟进度条最直接
没有真实后端接口时,前端常靠定时器“假装”加载中。核心是控制一个数字从 0 逐步增加到 100,再绑定到 DOM 元素的宽度或文本上。
关键点:别用 setTimeout 递归调用(易失控),setInterval 更可控;记得在达到 100 后 clearInterval,否则内存泄漏。
- 初始值设为
0,步长建议1~5,太小卡顿,太大不平滑 - 间隔时间选
50~200ms,100ms是较自然的节奏 - 避免在循环里频繁操作 DOM,先更新数据,再统一渲染
let progress = 0;
const timer = setInterval(() => {
progress += 2;
document.querySelector('.progress-bar').style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(timer);
}
}, 100);用 requestAnimationFrame 实现更流畅的动画
当对视觉平滑度有要求(比如上传大文件预览进度),requestAnimationFrame 比 setInterval 更合适——它会自动对齐屏幕刷新率,且页面切到后台时暂停,省资源。
但注意:它不保证固定时间间隔,只保证“下一帧”,所以不能直接当计时器用,需配合时间戳计算进度比例。
立即学习“Java免费学习笔记(深入)”;
- 记录开始时间
startTime = performance.now() - 每次回调中算出已过毫秒数,除以总耗时得到
0~1的完成比 - 仍需手动判断是否结束并停止递归调用
const startTime = performance.now(); const duration = 3000; // 总时长 3sfunction animateProgress() { const elapsed = performance.now() - startTime; const progress = Math.min(100, (elapsed / duration) * 100); document.querySelector('.progress-bar').style.width =
${progress}%; if (progress < 100) { requestAnimationFrame(animateProgress); } } requestAnimationFrame(animateProgress);
真实请求中如何把 XMLHttpRequest 的 onprogress 接出来
只有原生 XMLHttpRequest 支持上传/下载进度事件,fetch 目前不支持(虽有提案,但未落地)。如果你用的是 axios,它底层封装了 XMLHttpRequest,可通过 onUploadProgress 或 onDownloadProgress 钩子拿到。
重点:必须设置 Content-Length(服务端要返回 Content-Length 响应头),否则 event.total 为 0,无法算百分比。
-
event.loaded是已传输字节数,event.total是总字节数 - 上传时用
xhr.upload.onprogress,下载时用xhr.onprogress - 某些 CDN 或代理可能过滤掉
Content-Length,导致total === 0,此时只能显示“正在处理…”而非精确百分比
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
document.querySelector('.progress-bar').style.width = `${Math.round(percent)}%`;
}
};
xhr.send(file);用 AbortController 中断进度条逻辑
用户点了“取消”按钮后,不仅要终止请求,还要让进度条停在当前值或归零——否则会出现“请求已停,但进度条还在跑”的错觉。
常见误区:只调 abort(),却没清理定时器或动画帧。必须把所有异步控制句柄(intervalId、animationFrameId、controller)统一管理。
- 把
setInterval的返回值存到变量,abort时一起clearInterval - 用
requestAnimationFrame时,保存上次返回的 id,并在 abort 时用cancelAnimationFrame - 不要在
finally或catch里盲目重置进度为 0,得区分是成功结束、失败中断还是用户取消
进度条最难的不是怎么画,而是状态同步:请求、UI、用户操作三者之间稍有脱节,就会出现跳变、卡死或假完成。尤其在移动端弱网下,loaded 可能长时间不更新,这时候加个超时 fallback 比强行“动起来”更重要。










