纯CSS动态进度条的关键在于精准对接真实加载状态。应使用style.width+transition组合,JS控制width值,CSS负责过渡;推荐CSS变量--progress配合calc()实现组件化,或用background-position+linear-gradient、transform:scaleX()提升性能与兼容性。

纯 CSS 实现动态进度条,关键不在“能不能动”,而在“动得准不准、停不停得住、接不接得上真实加载”。硬写 @keyframes 跑一个 2s 的 width 动画,页面还没加载完它就结束了——这种不是进度条,是倒计时假动作。
用 style.width + transition 是最可控的组合
这是对接真实加载(如 fetch 的 progress 事件、XMLHttpRequest.upload、WebSocket 分块接收)的底线方案。CSS 只管过渡,JS 只管喂值。
- 进度条子元素必须设
width: 0%初始值,且父容器加overflow: hidden -
transition: width 0.3s ease-out必须写在子元素上,别写在父容器或伪元素里 - JS 更新时直接操作
el.style.width = (loaded / total * 100) + '%',不要用 classList 切预设 class - 当
total === 0(比如流式响应无总长),建议设为'0%'或加indeterminate类降级处理 - 避免
max-width、flex-basis、grid-column干扰,它们会截断 width 过渡链;可显式加max-width: none或flex: 0 1 auto
用 --progress 变量替代 style.width 更适合组件化场景
当进度条嵌在 React/Vue 组件里,或需要和伪元素、渐变背景联动时,CSS 变量比内联 style 更干净。但要注意 Safari 对 calc() 的兼容细节。
-
--progress建议存 0–100 整数(语义清晰,JS 计算方便),CSS 中用width: calc(var(--progress) * 1%) - 必须写
transition: width 0.3s ease,否则变量变了,宽度跳变 - Safari 15.6 之前不支持
calc(var(--progress) * 1%)中无单位变量,要写成calc(var(--progress) * 1% / 100)或改用小数(0–1) - 用开发者工具的 “Computed” 面板确认最终生效的
width是否随变量实时变化,别只看 Styles 面板
用 background-position + linear-gradient 实现高性能伪进度条
适用于页面级加载指示器(比如顶部细条)、不需要精确百分比、但要求动画丝滑不卡顿的场景。它走 GPU 合成层,不触发 layout。
立即学习“前端免费学习笔记(深入)”;
- 容器设
position: relative,伪元素::before设position: absolute覆盖 - 背景用
linear-gradient(to right, #4cd964, #4cd964),再配background-size: 200% 100% - 动画靠
background-position: 100% 0, 0 0→background-position: -100% 0, 0 0实现左移拉满 - 想让 JS 接管停止点?定义
--ui-loading-progress: 0在:root,用background-position: calc(var(--ui-loading-progress) * -100%) 0映射,再配transition: background-position 0.3s ease-out - 别在伪元素上加
border-radius再套动画,iOS Safari 容易重绘抖动
移动端循环动画必须避开 width,改用 transform: scaleX()
安卓低端机和 iOS Safari 上,width 动画掉帧明显。transform 是唯一能稳定跑 60fps 的属性。
- 用
@keyframes progress-fill { 0% { transform: scaleX(0); } 100% { transform: scaleX(1); } } - 动画时长设 2.5s,
animation-timing-function: linear(别用ease,视觉节奏失真) - 加
will-change: transform可提升首帧性能,但动画结束后务必用 JS 移除,否则长期占 GPU 内存 - 高度用
0.15vh而非固定 px,适配不同屏幕密度;避免用rem,用户缩放会破坏比例 - 如果后续要从循环切换到定格 100%,别删动画,改用
animation-play-state: paused+transform: scaleX(1)
真正难的从来不是让进度条动起来,而是让它在 JS 加载逻辑中断、网络超时、用户切页、服务端返回异常长度时,依然不闪、不跳、不残留旧状态——这些边界 case 才决定一个进度条是玩具还是生产可用。


















