用纯 CSS 和 JavaScript 实现自定义光标与动态聚光灯效果教程

作者:袖梨 2026-07-11

本文详解如何用 CSS radial-gradient 配合 CSS 变量和伪元素,创建可随鼠标移动、精准居中且真正“揭示内容”的聚光灯效果,避免 DOM 操作性能损耗。

本文详解如何用 css `radial-gradient` 配合 css 变量和伪元素,创建可随鼠标移动、精准居中且真正“揭示内容”的聚光灯效果,避免 dom 操作性能损耗。

要实现一个真正「揭示内容」的聚光灯效果(而非仅叠加发光圆),关键在于:将遮罩层(黑底+中心透光)作为背景层覆盖整个容器,并通过动态更新 background-position 实现光斑跟随鼠标移动。原方案中直接插入 <div> 并设置 radial-gradient 背景,本质上只是在顶层画了一个发光圆,无法穿透遮罩显示底层内容——这正是问题核心。

✅ 正确思路是:利用 .box::before 伪元素作为全屏遮罩层,其背景使用「中心透明 → 外圈全黑」的径向渐变;再通过 JS 实时更新 --flashlight-x-pos 和 --flashlight-y-pos 两个 CSS 自定义变量,驱动背景定位,让“光洞”始终对准鼠标位置。

以下是完整实现步骤:

1. HTML 结构(极简)

<div class="box">  <h1>Light</h1></div>

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('mouseleave', () => {  // 鼠标离开时,将光斑移出视区,实现平滑隐藏  box.style.setProperty('--flashlight-x-pos', '-300px');  box.style.setProperty('--flashlight-y-pos', '-300px');});

✅ 关键优势说明:

  • 真揭示效果:.box::before 的黑色遮罩层 + 中心透明区域,天然支持「透过光洞看到下方 <h1>」,无需额外图层叠加;
  • 高性能:避免频繁 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 而非 <img> 标签,避免 pointer-events: none 导致的点击穿透问题。

通过该方案,你将获得一个轻量、流畅、语义清晰的聚光灯交互效果——它不只是视觉装饰,而是真正「照亮并揭示内容」的实用 UI 组件。

立即学习“Java免费学习笔记(深入)”;

相关文章

精彩推荐