聚光灯效果本质是遮罩层+局部透明,通过径向渐变伪元素(::before)配合css变量与js实时坐标计算实现动态跟随,需设pointer-events: none、position: relative/absolute同上下文,并用requestanimationframe节流更新--x/--y变量确保平滑。

聚光灯效果的本质是遮罩层+渐变透明
不是靠 CSS 的 box-shadow 或 text-shadow 模拟,而是用一个圆形 ::before 伪元素盖在按钮上,中心透明、边缘全黑(或半透),再配合 transform 跟随鼠标移动。关键在于遮罩层必须和按钮同尺寸、同定位上下文,且 pointer-events: none,否则会拦截鼠标事件。
- 遮罩层的
background必须用径向渐变:radial-gradient(circle at var(--x) var(--y), transparent 0%, rgba(0,0,0,0.6) 70%) -
--x和--y需通过 JS 实时计算并设置为鼠标相对按钮左上角的偏移量(clientX - button.getBoundingClientRect().left) - 按钮本身要设
position: relative,遮罩层设position: absolute; top: 0; left: 0; width: 100%; height: 100%
用 mousemove 监听但别频繁触发重绘
直接在 mousemove 里反复调用 style.setProperty() 会导致卡顿,尤其在高刷屏或低配设备上。实际只需监听一次,用 requestAnimationFrame 节流,并只更新 CSS 变量值。
- 不要在事件回调里直接写
el.style.setProperty('--x', x + 'px')多次 —— 改成先存变量,再统一在raf回调里更新 - 注意:
getBoundingClientRect()返回的是相对于视口的坐标,减去按钮的left/top才是相对自身坐标 - 移动端没有
mousemove,需额外监听touchmove,且取touches[0]坐标
radial-gradient 的参数顺序和单位容易写错
很多人复制网上代码后效果不对,问题常出在渐变语法里:circle at 50px 50px 表示圆心在距左 50px、距上 50px 处,但若按钮宽高不固定,用像素值就失效;必须用 var(--x) 动态传入,且单位必须是 px(不能省略,也不能用 rem 或 %)。
- 错误写法:
radial-gradient(circle at var(--x) var(--y), ...)→ 如果--x是字符串"50"而没带单位,浏览器会忽略 - 正确写法:
el.style.setProperty('--x', (e.clientX - rect.left) + 'px'),确保拼出的是"50px" - 透明过渡段建议用
transparent 0%, rgba(0,0,0,0.6) 70%,太陡(比如 90%)会显得生硬,太缓(比如 40%)聚光感弱
按钮文字被遮罩“吃掉”怎么办
遮罩层默认叠在文字上方,导致悬停时文字变暗甚至看不清。解决办法不是降低遮罩透明度(那样聚光感就没了),而是给文字加一层独立的 z-index 提升,或者用 isolation: isolate 触发堆叠上下文隔离。
- 最稳妥方式:按钮内文字包一层
<span></span>,设position: relative; z-index: 2 - 避免用
z-index: 1给遮罩层,因为按钮本身没设z-index,它默认在文档流中,层级关系易混乱 - 如果按钮用了
background: linear-gradient,遮罩层叠加后颜色可能异常 —— 此时建议把背景色/渐变移到伪元素上,遮罩只负责明暗变化
CSS 变量 + JS 坐标计算 + 径向渐变三者必须严丝合缝,少一个环节就会变成“黑块乱飞”或“完全没反应”。移动端 touch 坐标处理、防抖节流、单位拼接这三点,最容易在调试时漏掉。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











