滚动进度条不能只用 position: sticky,因为它不感知滚动位置、无法计算百分比;必须用 JS 读取 scrollTop 和 scrollHeight 动态计算,并通过 CSS 变量驱动样式变化。

滚动进度条为什么不能只用 position: sticky
因为 position: sticky 本身不感知滚动位置,它只是让元素在滚动到某临界点时“粘住”,没法自动计算当前滚动百分比。想做出进度条,核心是把滚动高度映射成宽度或颜色变化,这必须依赖 JS 读取 scrollTop 和 scrollHeight —— CSS 单独做不到动态比例计算。
纯 CSS 实现的“伪进度条”适用场景和限制
如果你只需要一个固定位置、静态长度的视觉提示(比如顶部细条随页面进入而渐显),可以用 sticky 配合 transform: scaleX() + @keyframes 做简单入场动效,但无法真实反映滚动比例。
- 适合:单页长图介绍页、产品功能轮播页等滚动节奏可控、分段明确的场景
- 不适用:内容高度动态、需精确反馈阅读进度的文档/博客/后台列表页
- 兼容性注意:
sticky在 iOS Safari 15.4 以下对body直接生效有 bug,建议包裹一层<div class="scroll-container"> 并设 <code>height: 100vh; overflow-y: scroll;真正可用的轻量级实现:CSS + 最小 JS 绑定
只需监听
scroll,算出百分比,写入自定义属性,CSS 通过width或background-size响应——这样既保持样式可维护,又避免重排重绘。示例关键代码:
立即学习“前端免费学习笔记(深入)”;
document.addEventListener('scroll', () => { const progress = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100; document.documentElement.style.setProperty('--scroll-progress', `${progress}%`); });CSS 中这样用:
progress-bar { position: sticky; top: 0; height: 4px; background: linear-gradient(90deg, #3b82f6, #8b5cf6); width: var(--scroll-progress, 0%); }- 别用
requestAnimationFrame包裹这个监听——滚动事件本身已足够平滑,加了反而可能丢帧 - 注意除零:当
scrollHeight === clientHeight时,直接设--scroll-progress为100%,否则会 NaN - 移动端记得加
touch-action: manipulation到progress-bar,减少延迟
为什么不用 IntersectionObserver 模拟进度条
有人试图用多个
IntersectionObserver监听页面区块来拼进度,但实际会出问题:- 区块高度不确定时(比如图片懒加载后撑开),触发时机错乱
- 滚动过快时 observer 回调滞后,进度跳变明显
- 需要手动维护所有 section 的顺序和权重,复杂度远超直接读
scrollY - 无法处理用户拖拽滚动条这种非连续行为
所以,哪怕只是展示型进度条,也老老实实读
scrollY—— 它最直接,也最可靠。真正容易被忽略的是:滚动容器不一定是
window。如果页面用了局部滚动(比如overflow-y: auto的侧边栏或主内容区),scrollY就完全没用,必须换成对应容器的scrollTop和scrollHeight,且要监听那个容器的scroll事件,不是全局的。 - 别用


















