您现在的位置: 365建站网 > 365文章 > JS 进度条怎么写?scaleX 动画、真实上传与后台任务上报

JS 进度条怎么写?scaleX 动画、真实上传与后台任务上报

文章来源:365jz.com 点击数:10 更新时间:2025-02-08 14:39

js 进度条的实现分四类:CSS 静态条、JS 逐帧更新、真实进度(下载/上传)、以及顶部加载条(nprogress 式)。多数人写出的版本“能动但卡顿”“读不出真实百分比”,原因在动画手段选错和没有可量化的进度源。本文给出四种可用写法。

js进度条实现代码

一、纯 CSS 不确定进度(“转圈式”进度条)

只知道“在处理中”,不知道百分比时,用 CSS 动画最省:

<div class="bar indeterminate"><div class="fill"></div></div>

.bar { height:4px; background:#e6e8ef; overflow:hidden; position:relative; border-radius:2px }
.bar .fill { position:absolute; left:-40%; width:40%; height:100%;
             background:linear-gradient(90deg,#3b82f6,#60a5fa); }
.indeterminate .fill { animation:slide 1.1s infinite ease-in-out }
@keyframes slide {
  0%   { left:-40% }
  100% { left:100% }
}

注意动画走的是 left,其实更好的做法是 transform: translateX()——GPU 合成、不触发布局。生产建议改成 transform。

二、确定进度:用 transform 平滑更新

<div class="bar"><i style="transform:scaleX(.3)"></i></div>

.bar > i { display:block; height:100%; width:100%; transform-origin:left;
           background:#3b82f6; transition:transform .25s ease }

// JS:直接改 scaleX
function setProgress(p) {          // p: 0~1
  el.style.transform = 'scaleX(' + Math.max(0, Math.min(1, p)) + ')';
}

scaleX 比改 width 快得多(避免 layout)。配合 transition 天然平滑,不需要自己写缓动函数。

三、真实进度:必须有可量化的数据源

1. 文件上传(XHR 有 progress 事件,fetch 没有)

const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = e => {
  if (e.lengthComputable) setProgress(e.loaded / e.total);
};
xhr.onload  = () => setProgress(1);
xhr.onerror = () => { /* 失败状态,不要停在半截 */ };
const fd = new FormData();
fd.append('file', input.files[0]);
xhr.send(fd);   // 进度是真实字节比例

fetch 想用进度要走 ReadableStream + Content-Length,或用 axios 的 onUploadProgress/onDownloadProgress(底层仍是 XHR)。

2. 后端任务进度(导入、生成报表、转码)

这类没有字节概念,必须服务端上报。三种轮子,按可靠性排序:

  1. Redis 计数器 + 前端轮询:任务每处理一批 INCR done:{jobId},前端每 1~2 秒查 done/total。实现最简单,容错最好;
  2. SSE(Server-Sent Events):比 WebSocket 轻,服务端 text/event-stream 推事件,浏览器 new EventSource('/progress?id=123') 直接收,注意要关闭代理缓冲(Nginx proxy_buffering off;);
  3. WebSocket:双向、适合高频,但要多维护连接状态与重连逻辑。
// 前端:SSE 版
const es = new EventSource('/job/progress?id=' + jobId);
es.onmessage = ev => {
  const { done, total, state } = JSON.parse(ev.data);
  setProgress(total ? done / total : 0);
  if (state === 'finished' || state === 'failed') es.close();
};
es.onerror = () => es.close();   // 必须处理,否则会无限重连

3. 前端自己算:批量任务分片

async function processAll(list, onStep) {
  for (let i = 0; i < list.length; i++) {
    await doOne(list[i]);
    onStep((i + 1) / list.length);
    if (i % 20 === 19) await new Promise(r => setTimeout(r));  // 让出主线程,避免界面卡死
  }
}

那个 setTimeout 让帧很重要:不让出主线程,进度条就“走完最后一步才刷新”,看起来像卡住。

四、顶部加载条(页面跳转反馈)

nprogress 式“页面顶部的细条”其实是假进度(前 80% 快、后 20% 慢),目的是缩短感知等待。要点写法:

let t = 0, timer;
function start() {
  setProgress(0);
  timer = setInterval(() => {
    t = Math.min(0.92, t + (0.06 - t) * 0.2);   // 越接近完成涨得越慢
    setProgress(t);
  }, 120);
}
function done() { clearInterval(timer); setProgress(1); setTimeout(() => hide(), 300); }

注意 done() 后要有短暂停留再隐藏,否则闪一下就没了。

五、无障碍与体验细节

  • 进度条加 ARIA:role="progressbar" aria-valuenow="35" aria-valuemin="0" aria-valuemax="100" aria-label="上传进度",让读屏用户能获取状态;
  • 百分比要同时用文字显示(纯视觉进度对色觉障碍不友好);
  • 失败态要能重试,且不要把进度重置为 0(保留位置更符合心智);
  • 耗时任务提供“取消”,前端调 xhr.abort(),服务端要有对应的中断处理;
  • 不要用 setInterval(100) 固定步长模拟真实上传——一旦实际网络波动,进度会严重失真。

六、常见问题 FAQ

问:为什么进度到了 99% 就停住?
答:多数是“等待服务端最终返回”的阶段没算进进度。做法:把 onload 前设为 0.9,收到响应再置 1,并在 UI 上改成“处理中…”。

问:<progress> 标签能用吗?
答:能用(HTML 原生,<progress value="30" max="100">),但样式跨浏览器差异大,需要伪元素定制,工业项目仍多用自定义 div。

问:进度条动画掉帧?
答:检查是否在改 width/top/left(触发 layout),改 transform 后一般就顺了;同时避免同一帧里大量读写 DOM。

选型规则:不知道百分比 → CSS 动画;知道 → scaleX;上传下载 → XHR progress;后台任务 → Redis+轮询或 SSE;页面跳转反馈 → 假进度但别太久。

生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »