真滚动驱动视差核心是scroll事件+transform动态计算:监听滚动或用IntersectionObserver检测视口位置,结合requestAnimationFrame更新translateY等变换值,实现近快远慢的自然位移,避免background-attachment:fixed伪方案。

滚动驱动视差的核心是 scroll() + transform,不是 background-attachment: fixed
很多人一搜“视差”就套用 background-attachment: fixed,但它只是静态伪视差,不响应滚动速度、无法控制层级、在 Safari 和部分移动端表现异常。真滚动驱动视差必须靠 JS 监听滚动,动态计算并更新元素的 transform: translateY()(或其他轴向偏移)。
关键判断:如果你需要「不同元素以不同速率移动」「滚动到某区域才触发」「支持触屏惯性滚动」,就必须放弃纯 CSS 方案,走 scroll() 事件 + requestAnimationFrame 路线。
用 IntersectionObserver 替代频繁监听 scroll 事件
直接绑 window.addEventListener('scroll', ...) 容易卡顿,尤其在低端设备或复杂页面上。现代做法是用 IntersectionObserver 检测元素是否进入/离开视口,再结合 getBoundingClientRect() 算出相对滚动进度。
-
IntersectionObserver自带节流,不阻塞主线程 - 对每个视差元素单独观察,可精准控制触发时机(比如只在
ratio > 0.1时启用) - 配合
threshold: [0, 0.25, 0.5, 0.75, 1]能拿到更细粒度的滚动位置 - 注意:Safari 15.4+ 才完整支持
IntersectionObserver的rootMargin百分比单位,旧版本需用 px 值
transform: translateY() 的偏移量怎么算才自然
视差感来自「近处快、远处慢」,但直接用 window.scrollY * 0.5 这类固定系数会显得生硬。更合理的方式是绑定到元素自身在视口中的位置变化:
- 取元素顶部距视口顶部的距离:
const y = rect.top + window.scrollY - 设基准滚动范围(如视口高度的 1.5 倍):
const range = window.innerHeight * 1.5 - 归一化滚动比例:
const progress = Math.max(0, Math.min(1, (y - window.innerHeight / 2) / range)) - 再映射为位移:
element.style.transform = `translateY(${(1 - progress) * 80}px)`(正数往上移,模拟远层慢动) - 避免用
top/margin-top,它们会触发重排;transform只触发重绘,性能好得多
移动端触屏和 Safari 的三个硬坑
Safari 对 scroll 事件的触发时机、transform 的硬件加速策略、以及 IntersectionObserver 的精度都和 Chrome 不同,这几个点不处理,效果基本废一半:
- iOS Safari 默认禁用非被动
scroll事件监听,必须加{ passive: true },否则报Unable to preventDefault inside passive event listener - 视差元素必须加
will-change: transform,否则 Safari 可能跳帧或不启用 GPU 加速 - 触屏滚动有惯性,
scroll事件不会实时触发,得靠requestIdleCallback或setTimeout(..., 0)延后读取最终位置,否则位移滞后明显 - 别依赖
window.scrollY在touchmove中的即时值——它在 iOS 上只在touchend后更新一次
最稳的做法:所有位移计算统一放在 requestAnimationFrame 回调里,且每次只读一次 window.scrollY 或 getBoundingClientRect(),避免重复计算拉低帧率。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











