先说几个核心判断:用鼠标位置驱动背景偏移,听起来好像很复杂,但拆开看,无非就是三件事——监听鼠标位置、算出偏移比例、再喂给CSS。关键是怎么喂得优雅、喂得流畅,这才是差别所在。
核心机制其实很简单:通过mousemove事件捕获鼠标坐标,计算相对于容器中心点的归一化值(比如-50%到+50%),然后用setProperty把这两个值写入--x和--y自定义属性。CSS那边,用calc(50% + var(--x))配合background-position,就能实现平滑的偏移效果。不过,要做好这件事,还得注意几个细节:防抖处理、背景图尺寸必须大于100%、加上will-change来优化性能——这些一个都不能少。

用 mousemove 事件实时更新 --x 和 --y 自定义属性
简单来说,核心思路就是把鼠标在屏幕上的坐标位置,转化成CSS能直接使用的变量。然后这个变量,可以用于background-position,也可以用于transform,从而实现动态背景偏移效果。
实际操作中,直接监听mousemove事件,计算鼠标相对于容器中心的偏移比例,比如控制范围在-50%到+50%之间,然后通过setProperty将其存储为:root或元素级别的自定义属性。这里有一个容易踩坑的点:千万不要直接用clientX或clientY的原始像素值——那样背景图很容易跑出边界,出现错位。
一些实操建议:
- 事件绑定到目标容器上,而不是
window,否则页面滚动时坐标会乱掉 - 用
event.offsetX / container.offsetWidth来计算相对横坐标,再乘以100得到百分比 - 执行
container.style.setProperty('--x', xPercent + '%'),CSS里写background-position: calc(50% + var(--x)) calc(50% + var(--y)) - 别忘了加一个
throttle(比如30ms间隔),不然高频触发会拖慢渲染
用 background-position 实现视差式背景位移
相比transform,background-position更适合纯背景图的偏移场景——它不会触发重排,性能更友好。但前提是,背景图必须设置no-repeat,并且尺寸要大于容器,比如background-size: 120% 120%,否则偏移根本看不出来。
常见的问题和诊断方法:
- 背景图“卡住不动” → 检查
background-size是否小于等于100%。你知道吗?这个问题十个里有八个都是因为这个 - 偏移方向反了 →
--x为正时,背景其实向左移动,所以公式要用calc(50% - var(--x))来调整方向 - 边缘露白 → 把
background-size提高到130%,同时确保--x和--y的最大值不超过±15%
用 will-change: background-position 避免闪烁和掉帧
动态修改background-position,在某些浏览器(尤其是Safari和旧版Chrome)上,很容易触发重绘闪烁或卡顿。如果不做优化,每一帧都可能走一遍完整的布局→绘制流程,性能损耗非常大。
性能方面的关键点:
- 只对需要动画的背景元素加
will-change: background-position,别滥用在整个父容器上 - 配合
transform: translateZ(0)强制GPU加速,部分安卓WebView尤其需要这个 - 如果改用
transform来替代background-position(比如移动伪元素),记得设backface-visibility: hidden,防止iOS渲染异常
移动端适配:禁用 mousemove,改用 touchmove 并处理多点触控
手机上没鼠标,mousemove当然不触发。直接换成touchmove事件就行,但有个细节:touches[0]才是主触点,别用changedTouches——它只包含本次变化的点,容易出错。
使用场景要注意的差异:
- 单指滑动:取
event.touches[0].clientX,逻辑跟桌面端一样 - 双指缩放时背景会乱跳 → 在
touchstart里记录是否有多于一个触点,有则跳过更新 - iOS Safari有300ms延迟,加
touch-action: none到容器上可以消除这个延迟
麻烦的地方在于,touch坐标受页面缩放影响,而offsetX在移动端并不可靠。所以,统一用clientX - container.getBoundingClientRect().left来计算相对位置,才是最稳妥的做法。
