本文详细讲解如何借助 CSS radial-gradient、CSS 变量与伪元素,打造一个能够跟随鼠标移动、精准居中且真正“穿透遮罩以揭示内容”的聚光灯交互效果,同时有效避免 DOM 操作带来的性能损耗。
如果你曾被那种仅仅叠加一个发光圆、却无法穿透遮罩显示出底层内容的“伪聚光灯”所困扰,那么这篇文章正好能帮你彻底解决这个难题。其核心思路并不复杂:利用 CSS 的 radial-gradient 配合伪元素,构建一个“黑色背景 + 中心透光”的遮罩层,然后通过动态更新 background-position 属性,让光斑始终跟随鼠标移动。直接插入一个 并设置径向渐变背景,本质上只是在顶层绘制了一个圆形,根本无法穿透遮罩去显示下层内容——这才是问题的关键所在。
正确的实现方式是:将 .box::before 伪元素作为全屏遮罩层,其背景采用“中心透明 → 外圈全黑”的径向渐变;再通过 JavaScript 实时更新 --flashlight-x-pos 和 --flashlight-y-pos 这两个 CSS 自定义变量,驱动背景定位,使“光洞”始终精准对准鼠标指针的位置。
下面直接进入具体实现步骤。
1. HTML 结构(极简)
Light
2. CSS 样式(核心遮罩逻辑)
.box { display: flex; align-items: center; justify-content: center; cursor: none; width: 100%; padding: 5rem; background-color: #000; overflow: hidden; position: relative; /* 初始光斑偏移至可视区外,实现“隐藏”效果 */ --flashlight-x-pos: -200px; --flashlight-y-pos: -200px; --flashlight-inset: -300px; /* 控制遮罩层大小,确保完全覆盖 */}/* 聚光灯遮罩层(伪元素) */.box::before { content: ''; position: absolute; inset: var(--flashlight-inset); /* 全向扩展遮罩 */ background-image: radial-gradient( circle, transparent 0%, /* 中心完全透明 → 显示内容 */ rgba(0, 0, 0, 0.8) 60px, /* 过渡到半透明灰黑 */ #000 70px, /* 快速过渡为纯黑 */ #000 100% /* 完全覆盖 */ ); background-position: var(--flashlight-x-pos) var(--flashlight-y-pos); background-repeat: no-repeat; z-index: 1;}/* 自定义光标(也用伪元素,减少 DOM 节点) */.box::after { content: ''; position: absolute; width: 50px; height: 50px; background: url('https://picsum.photos/50') no-repeat center / contain; transform: translate(var(--flashlight-x-pos), var(--flashlight-y-pos)); pointer-events: none; z-index: 2;}
3. JavaScript(高效定位 + 变量驱动)
const box = document.querySelector('.box');// ⚠️ 关键:getBoundingClientRect 在事件外计算一次,避免 mousemove 中重复调用(性能敏感!)const rect = box.getBoundingClientRect();box.addEventListener('mousemove', (e) => { const x = e.clientX - rect.left; const y = e.clientY - rect.top; // 计算相对于容器中心的偏移量(使光斑自然居中) box.style.setProperty('--flashlight-x-pos', `${x - rect.width / 2}px`); box.style.setProperty('--flashlight-y-pos', `${y - rect.height / 2}px`);});box.addEventListener('mouselea ve', () => { // 鼠标离开时,将光斑移出视区,实现平滑隐藏 box.style.setProperty('--flashlight-x-pos', '-300px'); box.style.setProperty('--flashlight-y-pos', '-300px');});
✅ 关键优势说明:
- 真揭示效果:
.box::before的黑色遮罩层搭配中心透明区域,天然支持“透过光洞看到下方内容”,完全无需额外图层叠加,实现真正的聚光灯穿透显示。 - 高性能表现:无需频繁执行
appendChild或style.left/top操作,仅更新 CSS 变量,浏览器即可自行优化渲染流程,保证交互流畅度。 - 响应式友好:
background-position会自动适配容器缩放与页面滚动,无需手动重算坐标,在不同屏幕尺寸下表现稳定。 - 可定制性强:调整
radial-gradient的参数即可灵活控制光斑大小、边缘柔化程度以及遮罩透明度(例如使用rgba(0,0,0,0.9)可获得更暗的遮罩效果)。
⚠️ 注意事项:
- 请确保
.box具有明确的尺寸(如设置width/height或padding),否则getBoundingClientRect()的返回值可能不够稳定,影响光斑定位精度。 - 如果页面存在滚动行为,需要监听
scroll事件并重新计算rect,或者改用e.offsetX/e.offsetY(但其浏览器兼容性略低)。 - 自定义光标图片建议使用
background-image而非标签,以避免pointer-events: none导致的点击穿透问题,确保交互体验完整。
通过以上步骤,你就能获得一个轻量、流畅且语义清晰的聚光灯交互组件——它不仅是视觉上的装饰,更是真正能够“照亮并揭示内容”的实用前端 UI 工具。
