滚动渐变色需用scrollY映射色相环0–360,取document.documentElement.scrollHeight−innerHeight为maxScroll,clamp scrollY防负值超限,CSS变量+hsl()避免重绘,rAF节流+passive监听保帧率。

滚动时用 scrollY 动态算 hsl() 色相值
核心是把滚动距离映射到色相环(0–360)上,而不是直接用像素值。比如页面总高 2000px,滚动到 1000px 时,色相就该是 180;超过 2000px 就卡在 360。别用 window.scrollY / document.body.scrollHeight 直接除——body 高度可能不准,尤其有 margin 或 viewport 变化时。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
document.documentElement.scrollHeight替代document.body.scrollHeight,更可靠 - 限制滚动范围:取
Math.min(scrollY, maxScroll),避免色相超 360 导致跳变 - 色相计算推荐用线性映射:
const hue = (scrollY / maxScroll) * 360,maxScroll 设为document.documentElement.scrollHeight - window.innerHeight - 别忘了加
clamp或三元判断,防止初始滚动为 0 时 hue 为 NaN
CSS 自定义属性 + hsl() 实现无重绘渐变
直接改元素的 style.color 或 style.backgroundColor 会触发 layout,高频滚动下掉帧。正确做法是只更新一个 CSS 变量,让 CSS 自己算颜色。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在 :root 或目标元素上定义变量,如
--hue: 0 - 样式中写
background-color: hsl(var(--hue), 80%, 60%),浏览器会在渲染时自动插值 - 确保
hsl()的饱和度和亮度固定(或按需微调),只让 hue 变,否则颜色轨迹不可控 - 如果用
transition: background-color,滚动中会失效——CSS 变量本身不触发 transition,得靠 JS 手动加will-change: background-color或用requestAnimationFrame节流
JS 节流不是可选,是必须
原生 scroll 事件每秒触发几十次,每次读 scrollY、算 hue、设 CSS 变量,不节流就是白给主线程压力。Chrome DevTools 里看 FPS 掉到 30 以下,基本就是这里拖垮的。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
requestAnimationFrame最稳妥:在 scroll 回调里只标记“需要更新”,真正计算和赋值放在下一帧 - 别用
setTimeout(..., 16)模拟 rAF,精度差且易累积延迟 - 监听前加
{ passive: true },不然 Chrome 会警告并降级滚动性能 - 如果只是背景渐变,甚至可以考虑用
IntersectionObserver+getBoundingClientRect()做粗粒度替代,但精度不如 scrollY
移动端 iOS Safari 的 scrollY 坑
iOS Safari 在“橡皮筋”回弹时,window.scrollY 会变成负数或大于 scrollHeight,导致 hue 算出负值或超 360,颜色突然乱跳(比如从青变红再闪黑)。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 始终对
scrollY做 clamping:const y = Math.max(0, Math.min(scrollY, maxScroll)) - 别依赖
scrollY判断是否到底部——iOS 下scrollY === maxScroll几乎不会精确成立,要用scrollY >= maxScroll - 1 - 如果发现滚动结束瞬间颜色抖动,大概率是没处理好回弹阶段的负值,加个
Math.abs()不行,得截断 - 真要兼容极致体验,可以监听
touchend后强制一次 update,但多数场景 clamp 就够了
滚动渐变看着简单,实际卡点全在边界条件:最大滚动值怎么取、负值怎么截、变量更新时机怎么控。这些地方一松,颜色就跳、帧率就崩、iOS 就闪。别信“设个变量+写个监听”就完事,每个数字都要有明确含义和兜底。


















