滚动监听必须用javascript,css无原生scroll感知能力;可靠方案是用getboundingclientrect()计算元素视口占比,在requestanimationframe中更新css变量--scroll-progress并统一命名规范,避免hsl色相断层。

滚动监听必须用 JavaScript,CSS 本身不感知 scroll
CSS 没有原生的 scroll 伪类或函数,background: linear-gradient() 或 color 无法自动随滚动位置变化。所谓“纯 CSS 实现”实际是误导——哪怕用 scroll-timeline(目前仅 Chromium 114+ 支持且需显式启用),也得配合 @keyframes 和元素绑定,且不支持颜色插值到任意 DOM 节点上。
真正可控、跨浏览器的方案,是用 JavaScript 监听 scroll 事件,把滚动进度映射为数值,再注入 CSS 变量。这是目前最稳的路径。
用 getBoundingClientRect() 算可视区域占比更可靠
别直接用 window.scrollY / document.body.scrollHeight——它受页面缩放、滚动条宽度、动态内容插入影响,容易跳变或归零。更健壮的做法是监听目标容器(比如 header)相对于视口的位置。
- 监听元素需设
position: relative或有明确布局上下文,否则getBoundingClientRect()返回值可能意外为{ top: 0, bottom: 0 } - 用
rect.top和rect.height计算进入/离开视口的比例,例如:const progress = Math.min(1, Math.max(0, (viewportHeight - rect.top) / (viewportHeight + rect.height)))
- 在
requestAnimationFrame里更新,避免频繁触发重排
--scroll-progress 这个变量名要统一且可预测
CSS 变量名不是随便起的。如果 JS 设置的是 document.documentElement.style.setProperty('--scroll-p', p),但 CSS 里写的是 color: hsl(200, 100%, calc(50% + var(--scroll-progress) * 20%)),那变量根本对不上,颜色就不会动。
实操建议:
- 变量名全小写 + 连字符,如
--scroll-y、--header-progress,避免大小写混淆 - 始终用
document.documentElement设置,确保全局可读;不要用document.body,某些浏览器下 body 的style不继承到子元素 - 初始值在 CSS 里声明,比如
:root { --scroll-progress: 0; },防止 JS 尚未执行时样式崩塌
渐变色别硬套 hsl() 插值,先验色相断层
HSL 插值在跨越色相边界(比如从 hsl(350, ...) 到 hsl(10, ...))时会经过紫灰脏色,视觉上像“闪一下”。这不是代码 bug,是色彩模型限制。
安全做法:
- 优先用
lch()(如果目标环境支持),它对人眼感知更线性,且无色相翻转问题 - 若必须用 HSL,限定起止色相差
,或改用 RGB 十六进制插值(通过 JS 计算中间值后 setProperty) - CSS 里避免写
calc(var(--p) * 360)动态生成色相——没做归一化的话,超出 360 就乱转
滚动渐变真正的复杂点不在怎么动,而在怎么不动:防抖阈值、视口判定逻辑、色彩空间一致性,这三块漏一个,用户滑动时就会觉得“卡”“跳”“脏”。别信一键封装的 npm 包,先手写一遍 scroll + requestAnimationFrame + setProperty,才明白哪行在拖慢主线程。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











