视差滚动需通过 window.scrollY 计算并用 transform: translateY() 实现分层位移,禁用 background-attachment: fixed;必须用 requestAnimationFrame 节流、处理 touchmove 被动监听,并合理设置 will-change 与定位。

视差滚动不是靠某个 HTML 标签或 CSS 属性一键开启的,它本质是「滚动位置 + 分层位移」的联动计算。核心就一句话:window.scrollY 读出来,乘上不同系数,再用 transform: translateY() 打到各层元素上。
为什么 background-attachment: fixed 不推荐用于现代视差
这个 CSS 方案看起来最简单,但实际问题一堆:
-
background-attachment: fixed在 iOS Safari 和部分安卓 WebView 中不生效或表现异常,尤其页面有overflow: hidden或嵌套滚动时 - 无法控制速度系数(只能“固定”或“跟随”,没有 0.3 倍速这种中间态)
- 背景图会随视口缩放失真,且不能和前景元素做精确对齐(比如文字飘在云层上)
- 触发
will-change和图层合成困难,容易导致低端机掉帧
必须用 transform: translateY() 而不是 top/margin
用 top 或 margin-top 移动元素会强制浏览器重排(reflow),而 transform 只触发重绘(repaint),性能差一个数量级。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 所有参与视差的元素必须加
position: relative或position: absolute,否则transform可能不生效 - 务必加
will-change: transform(仅对频繁动画的层),让浏览器提前分配合成图层 - 避免给
body或html直接加视差——它们可能被用户缩放、滚动锁定干扰
requestAnimationFrame 是节流关键,不是可选项
直接在 scroll 事件里改 style.transform,在快速滚动时每秒可能触发上百次,低端设备立刻卡顿。
正确做法是把滚动值缓存下来,用 requestAnimationFrame 统一在下一帧更新:
let lastScrollY = window.scrollY;
window.addEventListener('scroll', () => {
lastScrollY = window.scrollY;
});
<p>function updateParallax() {
const layer1 = document.querySelector('.bg-layer');
const layer2 = document.querySelector('.fg-layer');</p><p>layer1.style.transform = <code>translateY(${lastScrollY * -0.2}px)</code>;
layer2.style.transform = <code>translateY(${lastScrollY * -0.8}px)</code>;</p><p>requestAnimationFrame(updateParallax);
}
updateParallax();注意:requestAnimationFrame 不是防抖,它只是保证“只在下一帧执行一次”,避免 scroll 事件密集触发导致样式反复计算。
移动端必须处理 touchmove 和 passive 监听
iOS Safari 默认禁用 window.scrollY 在 touchmove 过程中的实时更新,导致手指拖拽时视差冻结。
解决方案:
- 监听
touchmove并调用event.preventDefault()(仅对视差容器,别影响全局滚动) -
scroll监听器加{ passive: false },否则 iOS 会忽略 preventDefault - 避免在
touchmove里直接改样式,仍走requestAnimationFrame链路
真正难的从来不是写几行 transform,而是系数怎么配、图层怎么分、移动端怎么不卡、缩放后怎么不崩——这些细节没压住,视差就只是个看着炫、用着糊的半成品。



















