直接用 transform: translate() 卡顿是因为每次 mousemove 都触发同步重排重绘,且不自动启用 gpu 加速;改用 css 变量 + calc() 配合 requestanimationframe 节流可避免主线程阻塞,实现流畅视差效果。

为什么直接用 transform: translate() 会卡顿
每次 mousemove 触发时,若直接写 element.style.transform = `translate(${x}px, ${y}px)`,浏览器必须同步重排(Layout)→ 重绘(Paint)→ 合成(Composite),尤其在 60fps 下每 16ms 就触发一次,极易掉帧。更关键的是,纯 JS 改 style 不自动启用 GPU 加速层——即使加了 will-change: transform,也绕不开主线程阻塞。
用 CSS 变量 + calc() 实现轻量视差
JS 只负责把归一化坐标存进根元素变量,CSS 用 calc() 实时读取并计算位移,全程不触碰 DOM 样式树,合成层自动响应。
- 监听
mousemove时用requestAnimationFrame节流,避免连发 - 把
clientX/clientY除以window.innerWidth/window.innerHeight,转为 0–1 范围 - 存入:
document.documentElement.style.setProperty('--mouse-x', x); - 目标元素样式写:
background-position: calc(50% + var(--mouse-x) * 20px) calc(50% + var(--mouse-y) * 20px); - 务必设
background-attachment: scroll(显式声明,防继承干扰)
background-position 和 transform 的选择陷阱
别为了“看起来更酷”强行用 transform 模拟背景位移——background-position 是唯一能被 CSS 变量 + calc() 原生驱动的背景属性。用 transform 移动整个元素,会破坏背景图与容器的相对定位关系,且无法和 background-size: cover 协同工作。
- 背景图尺寸建议至少是容器宽高的 1.5 倍,否则位移时边缘露白
-
background-repeat: no-repeat必须显式设置 - 若需多层视差(如前景图+中景图+背景图),每层用不同系数:
calc(50% + var(--mouse-x) * 10px)、calc(50% + var(--mouse-x) * 5px)
移动端鼠标事件不存在,得 fallback
手机没有 mousemove,但有 touchmove;不过触摸点坐标不是 clientX/Y,而是 touches[0].clientX/Y。更重要的是:iOS Safari 对 touchmove 的节流更激进,requestAnimationFrame 未必能兜住。
- 检测是否为触摸设备:
'ontouchstart' in window - 触摸场景下,优先用
touchstart+touchmove替代mousemove - 系数要调小(比如从 20px 降到 8px),否则手指微动就导致背景猛偏
- 真要兼容性兜底,可降级为静态背景或仅保留滚动视差
实际最易被忽略的点:CSS 变量值更新后,background-position 的计算结果不会自动触发重绘优化——必须确保目标元素已处于合成层(例如加 will-change: background-position 或父级有 transform: translateZ(0)),否则仍可能卡。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











