最省事的进度条实现是用CSS自定义属性--ui-loading-progress配合@property声明为<number>类型,通过background-size映射进度,仅触发合成层重绘、不掉帧;需避名冲突、防NaN、节流更新并确保元素可见。

用--progress控制background-size最省事
直接把进度值映射成背景宽度,比拼接渐变字符串或反复改width更干净。关键不是“能不能动”,而是“动得稳不稳”:background-size只触发合成层重绘,不重排,滚动中也不会掉帧。
常见错误是写成background: linear-gradient(...) 0% 100% / var(--progress) 100%——这样var(--progress)必须带单位(如50%),否则无效;更稳妥的是拆开写:
background: linear-gradient(to right, #4cd964, #4cd964) left / var(--progress) 100% no-repeat; background-color: #f0f0f0;
-
--progress初始设为0%,JS里用element.style.setProperty('--progress', '67%')更新 - 别用
calc()包裹变量再传入background-size,部分 Safari 版本解析失败 - 移动端加
will-change: background-size能提前触发 GPU 加速,但仅在动画频繁时启用,避免滥用
用--ui-loading-progress替代--progress防冲突
第三方 UI 库(比如 Ant Design、Chakra UI)很可能也定义了--progress,直接覆盖会导致按钮、滑块等组件异常。命名必须带业务前缀,且尽量避开通用词。
真实项目里,你可能同时有上传进度、API 加载、资源预加载三套进度条,它们的变量名要能区分场景和生命周期:
立即学习“前端免费学习笔记(深入)”;
-
--ui-upload-progress:文件上传,范围0–100,整数 -
--ui-api-progress:fetch 请求,可能含小数(如37.2),需保留一位精度 -
--ui-loading-progress:首屏加载,配合animation-fill-mode: forwards停在100后不回退
变量值建议统一用无单位数字(如67),CSS 内部用calc(var(--ui-loading-progress) * 1%)转百分比,方便 JS 计算和比较。
@property声明变量类型才能动画插值
光靠transition: --ui-loading-progress 0.3s不会动——浏览器默认把自定义变量当字符串处理,无法做数值插值。必须显式声明它是<number>类型:
@property --ui-loading-progress {
syntax: '<number>';
inherits: false;
initial-value: 0;
}没这句,哪怕你用requestAnimationFrame一帧帧设值,动画也会跳变或卡住。目前支持 Chrome 110+、Edge 110+、Safari 16.4+;Firefox 尚未支持,需降级到transform: scaleX()或width方案。
- 声明后,
transition和@keyframes都能正确插值,包括cubic-bezier(0.34, 1.56, 0.64, 1)这类复杂缓动 - 别在
@keyframes里直接写--ui-loading-progress: 100——它不支持变量赋值,只能用background-size: calc(var(--ui-loading-progress) * 1%) 100%间接响应 - 如果用 Emotion 或 Styled Components,确保
@property注入时机早于组件渲染,否则首帧会闪
JS 更新--ui-loading-progress时注意除零和边界
真实加载中total可能为0(比如空响应、流式 SSE 没发Content-Length),这时直接算loaded / total * 100会得Infinity或NaN,导致 CSS 变量失效,进度条卡死。
安全写法是加一层保护:
const progress = total > 0 ? (loaded / total) * 100 : 0;
element.style.setProperty('--ui-loading-progress', String(Math.min(100, Math.max(0, progress))));- 始终用
Math.min/max钳制在0–100区间,避免负值或超 100% 导致背景溢出 - 用
String()转字符串再传入,避免某些旧版 Chromium 对数字类型变量解析不稳定 - 如果进度来自 WebSocket 分块消息,别每条都触发
setProperty——节流到 16ms(≈60fps)以内,否则 CSS 引擎来不及响应
最易被忽略的是:变量更新后,若元素尚未挂载或display: none,值虽已设,但动画不会启动。得确保元素处于渲染树中且可见,否则得手动触发重绘(比如getComputedStyle(el).opacity)。


















