直接用 JS 修改 style.transform 会卡顿,因频繁触发重排重绘;应改用 JS 注入 CSS 变量(如 --mouse-x/--mouse-y),再由 CSS calc() 结合 transform 计算偏移,使渲染交由合成器处理,配合 will-change: transform 实现 60fps。

为什么直接用 transform: translate() 做鼠标视差会卡顿
因为每次 mousemove 触发时,如果直接用 JS 修改元素的 style.transform,浏览器要反复重排重绘,尤其在低端设备或复杂 DOM 下,帧率很容易掉到 30fps 以下。更关键的是,这种写法把坐标计算和渲染逻辑耦合在一起,后续想调速、加阻尼、切响应式都得改 JS 逻辑。
真正轻量又可控的做法是:JS 只负责把鼠标坐标存进 CSS 自定义属性,CSS 用 calc() 和 transform 拿这些变量做偏移。这样渲染完全交给合成器(compositor),不触发 layout 或 paint。
实操建议:
- 在
document.documentElement上设置两个 CSS 变量:--mouse-x和--mouse-y,初始值设为50vw/50vh,避免首次渲染错位 - 监听
mousemove,用event.clientX和event.clientY更新变量,注意除以window.innerWidth/window.innerHeight归一化成 0–1 范围,方便后续缩放控制 - 别用
pageX/Y—— 它包含滚动偏移,会导致视差在页面滚动时跳变
如何用 CSS 变量 + calc() 实现可调速的视差层
CSS 本身不能读取鼠标位置,但能响应变量变化。核心思路是:把归一化的坐标(0–1)转成像素偏移,并乘一个「深度系数」来模拟不同图层距离。
立即学习“前端免费学习笔记(深入)”;
比如一层背景希望动得少(远),另一层图标动得多(近),就给它们不同的系数:
.layer-bg {
transform: translate(calc((var(--mouse-x) - 0.5) * -10px), calc((var(--mouse-y) - 0.5) * -10px));
}
.layer-fore {
transform: translate(calc((var(--mouse-x) - 0.5) * -40px), calc((var(--mouse-y) - 0.5) * -40px));
}说明:
-
var(--mouse-x) - 0.5把 0–1 映射成 -0.5~+0.5,让鼠标居中时偏移为 0 - 乘数(
-10px、-40px)就是视差强度,负号决定方向;数值越大,动得越明显 - 所有计算都在 CSS 中完成,JS 不参与渲染,所以加个
will-change: transform就能稳 60fps
移动端适配时 mousemove 为什么不触发
因为 iOS 和 Android 的触摸事件默认不发射 mousemove,除非显式开启 touch-action: none 或监听 touchmove。但直接绑 touchmove 有坑:它不提供 clientX/Y,而是 touches[0].clientX,且频繁触发容易过载。
更稳妥的解法:
- 用
PointerEvent统一处理:addEventListener('pointermove', handler),它兼容鼠标、触摸、笔输入 - 在 handler 里加防抖:用
requestAnimationFrame批量更新变量,而不是每次事件都 setProperty - 对纯触摸设备(如 iPad),可以关掉视差——加个
matchMedia('(hover: none)')判断是否无悬停能力,直接跳过绑定
为什么视差在滚动后偏移错乱
问题出在用了 clientX/clientY 却没考虑页面滚动。虽然它们本意是视口坐标,但如果容器设置了 transform、filter 或 perspective,某些浏览器(尤其是 Safari)会把 clientX/Y 解析成布局坐标而非视觉坐标,导致视差中心偏移。
修复方式很简单:
- 确保根容器(
html或body)没有意外的transform或overflow: hidden - 更新变量时,用
event.clientX - window.scrollX和event.clientY - window.scrollY冗余校正(虽然理论上不必要,但 Safari 16+ 仍偶发此问题) - 如果页面有 fixed 元素干扰,把视差逻辑限定在某个
relative定位的容器内,用getBoundingClientRect()算相对坐标
最易被忽略的一点:CSS 变量作用域。如果把 --mouse-x 设在 body 上,而视差元素在 shadow DOM 里,变量不会透传——必须设在 :root 或显式继承。


















