必须用 CSS 自定义属性 + JavaScript 监听 + transform: scaleX() 驱动,因 scroll-behavior: smooth 仅支持锚点跳转动画,而 width + transition 会触发重排、iOS 上失效或卡顿,且 progress 元素不支持 width 过渡。

直接用 scroll-behavior: smooth 只能控制锚点跳转的滚动动画,不能反映当前滚动进度;真要实现“随页面滚动实时更新的平滑进度条”,必须靠 CSS 自定义属性 + JavaScript 监听 + transform: scaleX() 驱动,否则 iOS 上容易卡顿、跳变或静默失效。
为什么不能只靠 width + transition 更新进度条
在滚动过程中频繁读写 width 会触发重排(reflow),尤其在 iOS Safari 中,scroll 事件回调里直接设 element.style.width = ... 极易导致动画降级甚至中断。更糟的是,progress 元素的 ::-webkit-progress-value 在 iOS 上根本不支持 width 过渡——它只认 background-color 动画,且需显式设置容器背景色才能生效。
- 常见错误:给
.progress-bar设transition: width 0.3s,再在scroll里反复改style.width→ 真机上进度条要么不动,要么突变 - 正确思路:把进度值抽成
--scroll-progress变量,用transform: scaleX()映射它,避免重排 - 务必加
will-change: transform,告诉浏览器这个元素要动画,尤其在position: sticky或长列表中
如何用 --scroll-progress + transform: scaleX() 实现平滑驱动
核心是把滚动位置映射为 0–1 的小数,存进 CSS 变量,再让进度条子元素用 transform: scaleX(var(--scroll-progress, 0)) 展开。容器必须设 overflow: hidden,子元素设 transform-origin: left center,否则缩放会溢出或错位。
- HTML 结构保持语义:
<div class="scroll-progress-bar"><div class="progress-fill"></div></div> - CSS 关键部分:
.scroll-progress-bar { height: 3px; background: #e0e0e0; overflow: hidden; } .progress-fill { height: 100%; background: #4CAF50; transform-origin: left center; will-change: transform; transform: scaleX(0); transition: transform 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .scroll-progress-bar::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; transform: scaleX(var(--scroll-progress, 0)); background: inherit; } - JS 更新只需一次赋值:
document.documentElement.style.setProperty('--scroll-progress', progressValue),不操作 DOM 宽度
滚动监听必须用 requestAnimationFrame 节流
直接在 scroll 或 touchmove 里计算并设变量,会高频触发,iOS Safari 会丢帧或强制同步渲染,导致进度条卡在某个值不动。切后台时 visibilitychange 事件也得处理,否则用户切回来进度条可能停在中间。
立即学习“前端免费学习笔记(深入)”;
- 缓存
document.body.scrollHeight和window.innerHeight,不在滚动回调里重复读取 - 用
requestAnimationFrame包一层更新逻辑,确保每帧最多执行一次:let ticking = false; function updateProgress() { if (!ticking) { requestAnimationFrame(() => { const scrollTop = window.pageYOffset; const maxScroll = document.body.scrollHeight - window.innerHeight; const progress = Math.min(1, Math.max(0, scrollTop / maxScroll)); document.documentElement.style.setProperty('--scroll-progress', progress.toFixed(3)); ticking = false; }); ticking = true; } } - 监听
visibilitychange,切后台时补一帧:document.addEventListener('visibilitychange', () => { if (document.hidden) updateProgress(); })
真正难的不是写几行 CSS,而是滚动时机与浏览器渲染管线的对齐——iOS Safari 对合成层调度极敏感,transform + will-change + requestAnimationFrame 这三者缺一不可,漏掉任一个,进度条就容易在快速滚动时“掉帧”或“回弹”。


















