视差滚动本质是JavaScript监听scroll事件,动态设置各层元素transform: translateY()实现速度差;背景层系数小(如0.2)、前景层系数大(如0.8),需配合requestAnimationFrame节流、position定位及z-index分层。
视差滚动不是某个现成的 HTML 标签或属性,而是靠监听 window.scrollY,动态调整不同层级元素的 transform: translateY() 值来模拟远近移动速度差异。纯 CSS 方案(如 background-attachment: fixed)兼容性差、控制粒度粗,现代实现基本依赖 JavaScript 驱动。
关键点在于:背景层位移量 = -scrollY × 0.3,内容层位移量 = -scrollY × 0.1,系数越小“越远”,越大越“近”。系数为 0 就是普通滚动。
常见错误现象:transform 直接写死固定值、没加 will-change: transform 导致卡顿、用 top 替代 transform 触发重排。
position: relative 或 absolute,否则 transform 可能不生效<div id="parallax-container">),便于统一计算基准touchmove 并 preventDefault(),否则 iOS Safari 会禁用 scrollY 更新直接在 scroll 事件里改样式会导致频繁重绘,尤其低端设备容易掉帧。正确做法是把滚动位置缓存下来,用 requestAnimationFrame 节流更新视差位移。
立即学习“前端免费学习笔记(深入)”;
示例核心逻辑:
let lastScrollY = window.scrollY;function updateParallax() { const currentScrollY = window.scrollY; const diff = currentScrollY - lastScrollY; // 更新各层偏移:layer1.style.transform = `translateY(${currentScrollY * 0.2}px)` lastScrollY = currentScrollY;}window.addEventListener('scroll', () => { if (!rafId) rafId = requestAnimationFrame(updateParallax);});
注意:requestAnimationFrame 不是防抖,而是确保只在下一帧执行一次,避免 scroll 事件密集触发导致样式反复计算。
setTimeout(..., 0) 或 debounce(16) 替代,它们无法保证与屏幕刷新同步lastScrollY,不能共用一个全局变量updateParallax(),否则初始位置不匹配很多教程推荐用 background-attachment: fixed 实现简易视差,但它在 iOS Safari 和部分 Android WebView 中被禁用(出于性能考虑),表现为背景图随内容一起滚动,毫无视差效果。
这不是 bug,是 Safari 的明确行为。检查是否生效只需打开 Safari 开发者工具 → Elements → 查看 computed styles 中 background-attachment 是否被标为 invalid 或直接忽略。
background-position: center calc(50% + ${offset}px),再配合 background-repeat: no-repeat
fixed,必须加降级逻辑:@supports not (background-attachment: fixed) { ... } 里补上 JS 方案background-size: cover 时,background-position 的百分比计算会受缩放影响,建议统一用 px 单位做偏移视差不是简单地让背景“往后跑”,而是要让视觉层次符合物理直觉:远处的云应该在山后面,山在建筑后面,建筑在文字前面。但默认情况下,所有元素都在同一 stacking context,z-index 不起作用。
必须为每一层显式创建 stacking context,否则无论怎么调 z-index,层叠顺序都由 HTML 书写顺序决定。
position: relative + z-index 是最简方式,例如:<div class="layer" style="z-index: 1;">(远)→ z-index: 10(近)transform(比如视差位移),它自动成为 stacking context,此时 z-index 仍有效,但仅对它的子元素生效transform 层可能意外遮挡其他层,建议在 touchstart 时临时移除视差逻辑,缩放结束后再恢复真正麻烦的从来不是怎么动起来,而是动起来之后,哪一层该在前、哪一层该透出边缘、缩放时是否撕裂——这些细节没处理好,用户第一反应就是“卡”或者“怪”。