用scroll做视差易卡顿,必须节流+requestAnimationFrame+transform;应绑定元素视口相对位置计算progress再映射位移,结构用relative定位,禁用background-attachment:fixed。

直接用 scroll 事件做视差容易卡顿,核心在于高频触发 + 频繁 DOM 计算。节流不是可选项,而是必须步骤;但光靠时间戳节流还不够,得配合 requestAnimationFrame 和 transform 才算真正高性能。
为什么必须节流?
滚动时浏览器每秒可能触发 60–100 次 scroll 事件。如果每次都在回调里读取 getBoundingClientRect()、计算偏移、再写 element.style.transform,会频繁触发回流(layout)和重绘(paint),主线程立刻过载,动画掉帧、拖影、甚至卡死。
节流的目标是把执行频率控制在浏览器刷新节奏内(比如每 16ms 一次),既保持视觉连贯,又避免资源争抢。
推荐的节流组合:rAF + passive + transform
不建议手写定时器节流(如 setTimeout 套壳),更优解是让逻辑对齐屏幕刷新周期:
- 监听 scroll 时加 { passive: true },告诉浏览器“我不会调 preventDefault()”,让原生滚动更早开始、更顺滑
- 用 requestAnimationFrame 包裹实际更新逻辑,确保只在下一帧渲染前统一读位置、算偏移、设样式
- 所有位移都用 transform: translateY(),启用 GPU 加速,不触发布局
视差位移怎么算才自然?
别用固定系数乘 scrollY(比如 scrollY * 0.3),那样会脱离元素自身位置,滚动到页面底部时效果突兀。更合理的方式是绑定到元素在视口中的相对运动:
- 用 getBoundingClientRect().top 获取元素顶部距视口顶部的距离
- 结合 window.innerHeight 定义一个滚动影响范围(例如 1.2× 视口高)
- 归一化出一个 0–1 的 progress 值,再映射为 translateY 偏移量
- 例如:远层元素用
translateY(${progress * 40}px),近层用translateY(${progress * 120}px)
结构与定位要避开常见坑
视差元素本身不能破坏文档流:
- 用 position: relative,安全且保留占位;别用 absolute 或 fixed,否则易遮挡内容或高度塌陷
- 外层容器要是正常滚动上下文(比如 body 或某个
overflow-y: auto的 div),监听对象也得对应——不是所有场景都监听 window - 背景图别依赖 background-attachment: fixed,它在 Safari、iOS WebView 和部分安卓浏览器中渲染异常,也不支持多层变速、旋转、缩放等复合效果










