不能只靠 :root 变量驱动滚动进度,因其静态不可响应滚动;纯 CSS 方案依赖数学巧合且易受 viewport 变化、动态内容、容器限制影响;JS 监听滚动并更新 :root 变量才是可靠路径。

为什么不能只靠 :root 变量驱动滚动进度
:root 变量本身是静态的,无法自动响应滚动位置变化。你设 :root { --scroll: 0 } ,它就永远是 0,除非 JS 主动更新。伪元素(如 ::before)可以读取这个变量并渲染,但“谁来改这个值”才是关键——CSS 没有内置的滚动监听机制,:root 只是容器,不是传感器。
用 ::before + linear-gradient 做“视觉模拟”的真实限制
这类纯 CSS 方案本质是把整个页面当做一个巨大渐变背景,再用固定定位遮罩层裁剪出“可见进度段”。它不计算真实滚动比例,而是依赖 background-size 和 calc(100% - 100vh) 的数学巧合。
- 进度条起始点可能不是 0%:body 高度包含 margin/padding,或初始滚动偏移未归零时,
top: 0遮罩会漏出一段 - 滚动到底部时经常卡在 95–98%,因为
100vh在 iOS Safari 地址栏收起/展开时会跳变,calc算不准剩余可滚动高度 - 如果页面内容动态加载(比如图片懒加载后撑开高度),渐变背景不会重绘,进度条直接失准
- 必须确保
body是唯一可滚动容器;若用了overflow-y: scroll的局部容器,这套方案完全失效
JS 更新 :root 变量才是可靠路径
真正可控的做法是:JS 监听滚动、算出百分比、写入 :root 自定义属性,CSS 仅负责消费该变量。这样既保持样式分离,又保证精度。
- 用
document.documentElement.scrollTop而非window.scrollY,iOS Safari 下更稳定 - 分母必须是
document.documentElement.scrollHeight - window.innerHeight,漏减会导致超 100% - 加
Math.min(1, ...)防止浮点误差溢出,避免width: 100.0000001%触发意外重排 - 更新逻辑放进
requestAnimationFrame,否则高频scroll事件直接改样式会卡顿 - CSS 中用
transform: scaleX(var(--scroll-progress, 0))替代width,走合成层,不触发 layout
移动端 fixed 进度条闪动怎么压住
iOS Safari 对 position: fixed 元素在地址栏切换、键盘弹出时有重绘异常,单纯加 z-index 无效。
立即学习“前端免费学习笔记(深入)”;
- 给进度条父容器加
will-change: transform,提前升层 - 避免同时设置
left和right,选一边定位(推荐left: 0; right: 0比left: 0; width: 100%更稳) - 真机测试必须打开,模拟器不触发 bounce clamp 行为
- 若仍闪动,降级方案:改用
position: sticky; top: 0,但需确保外层有height: 100vh; overflow-y: scroll容器包裹 body
实际最易被忽略的是:滚动事件里没做节流,又没包 requestAnimationFrame,结果低端安卓机上进度条抖动明显,用户一滚就晕。这不是 CSS 写得不够炫,是 JS 更新节奏没控住。


















