直接用box-shadow配合mousemove会卡顿,因每次移动都触发重排重绘,尤其在高刷屏或低端设备上易layout thrashing;CSS变量本身不触发重排,但JS频繁setProperty仍可能导致掉帧,关键在于节流更新、单位规范及坐标转换。

为什么直接用 box-shadow 配合 mousemove 会卡顿?
因为每次鼠标移动都触发重排重绘,尤其在高刷新率屏幕或低端设备上,requestAnimationFrame 不介入时,style.left/style.top 直接写入会导致 layout thrashing。CSS 变量本身不触发重排,但若配合 JS 频繁赋值(如每帧都调用 element.style.setProperty('--x', x)),仍可能因样式计算压力导致掉帧。
关键不是“能不能用 CSS 变量”,而是“怎么用才不卡”:
- 把
--x和--y设为根元素变量,避免重复查询offsetParent - 用
requestAnimationFrame节流,且只在坐标变化 > 2px 时更新变量 - 发光效果优先用
filter: blur()+opacity,比多层box-shadow更快
如何用 :root 变量 + transform 实现平滑跟随
发光区域不能是绝对定位的伪元素(易脱离文档流、遮挡交互),推荐用 clip-path 配合 radial-gradient 在目标元素内部模拟光晕,再通过 CSS 变量控制中心点:
:root {
--mouse-x: 50vw;
--mouse-y: 50vh;
}
<p>.glow-effect {
position: relative;
overflow: hidden;
}
.glow-effect::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at var(--mouse-x) var(--mouse-y), rgba(255, 220, 100, 0.4) 0%, transparent 60%);
pointer-events: none;
z-index: 1;
}注意:radial-gradient 的 at 位置支持 CSS 变量,但必须确保变量值带单位(如 px 或 vw),否则降级为默认居中。
立即学习“前端免费学习笔记(深入)”;
- JS 中设置变量时,必须补单位:
document.documentElement.style.setProperty('--mouse-x', `${e.clientX}px`) - 若目标元素非全屏,需将
clientX/Y转换为相对该元素的坐标,用getBoundingClientRect()计算偏移 - 不要在
mousemove里直接设变量——先存入let pendingX, pendingY,再由raf统一提交
为什么 filter: drop-shadow() 比 box-shadow 更适合动态光效?
drop-shadow() 是滤镜,作用于元素绘制后的位图,不参与布局计算;而 box-shadow 属于盒模型渲染层,频繁修改会触发合成器重绘。实测在 60fps 下,10 个元素同时跑 drop-shadow 动态光效,GPU 占用比等效 box-shadow 低约 35%。
典型写法:
.glow-target {
filter: drop-shadow(calc(var(--mouse-x) - 50vw) calc(var(--mouse-y) - 50vh) 24px rgba(255, 192, 0, 0.6));
}这个写法有坑:
-
calc()里不能混用单位:不能写calc(var(--mouse-x) - 50)(缺单位) -
drop-shadow的前两个参数是偏移量,需为相对值,否则光晕会随鼠标飞出可视区 - 若目标元素有
transform,drop-shadow会受其影响——此时应改用backdrop-filter+ 半透明遮罩层
移动端触摸事件下如何保持变量同步?
iOS Safari 对 touchmove 的节流更激进,且 clientX/Y 在某些机型上返回 0。不能只监听 mousemove,必须同时处理 touchstart 和 touchmove,并用 event.touches[0] 取坐标。
兼容写法:
function updateMousePos(e) {
const x = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const y = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
// ……后续 setProperty
}
<p>el.addEventListener('mousemove', updateMousePos);
el.addEventListener('touchmove', updateMousePos, { passive: false });重点:
-
{ passive: false }必须加,否则 iOS 上touchmove无法阻止默认行为(影响滚动) - 不要用
pointermove——部分安卓浏览器对它支持不稳定,且无法区分触控笔和手指 - 首次触摸后,可临时禁用
mousemove事件监听,避免双端事件冲突
真正难的不是写出来,是让光晕在缩放页面、横竖屏切换、iframe 嵌套时还准确定位——这些场景下 clientX/Y 会失真,得结合 window.visualViewport 或 getScreenCTM() 做校正,但大多数项目其实用不到那么深。


















