这个特效主要由 HTML、CSS3 和 JavaScript 协同完成,下面我将为你详细解释其原理,并提供一个可以直接复制使用的完整示例,以及一些更高级的实现思路。

(图片来源网络,侵删)
核心原理
想象一下你有一张图片(底层),上面覆盖了一层灰色的“保护膜”(上层),你的目标不是点击按钮,而是用鼠标在这层“保护膜”上“刮开”,显示出下面的图片。
这个效果的实现依赖于以下几个关键技术点:
-
HTML 结构:
- 一个容器,用于定位和放置内容。
- 一个底层元素,存放最终要显示的内容(例如图片、文字等)。
- 一个上层元素,作为可以被“擦除”的遮罩层。
-
CSS 样式:
(图片来源网络,侵删)- 定位: 使用
position: relative和position: absolute将底层和上层完美重叠在一起。 - 遮罩层样式: 上层遮罩层默认覆盖整个底层,并设置一个背景色(如灰色)或背景图片。
- 关键属性
mix-blend-mode: 这是实现效果的核心,通过设置mix-blend-mode: destination-out,我们可以让上层元素的绘制区域“挖空”下方的任何内容,当鼠标在这个上层元素上绘制时,绘制的部分就会变成透明,从而显示出底层的图像。 - 鼠标样式: 将鼠标指针设置为一个自定义的圆形或橡皮擦图标,增强用户体验。
- 定位: 使用
-
JavaScript 交互:
- 监听鼠标事件: 监听
mousedown(鼠标按下)、mousemove(鼠标移动)和mouseup(鼠标抬起)事件。 - 绘制擦除路径: 当鼠标按下并移动时,在 Canvas 上绘制一个路径,这个路径就是擦除的区域。
- 使用 Canvas: 我们将上层遮罩层实现为一个
<canvas>元素,因为 Canvas 提供了强大的2D绘图上下文,可以轻松地绘制图形(如圆形),并且其像素级操作与mix-blend-mode完美配合。
- 监听鼠标事件: 监听
完整代码示例(可直接复制运行)
这是一个最基础、最经典的实现方式,效果直观,易于理解。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">CSS3 鼠标擦除特效</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
font-family: sans-serif;
}
.scratch-container {
position: relative;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
overflow: hidden; /* 确保子元素不会溢出 */
}
/* 底层内容 - 最终要显示的内容 */
.scratch-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=1974&auto=format&fit=crop');
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 2.5em;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
/* 上层遮罩层 - 使用Canvas实现 */
#scratchCanvas {
position: absolute;
top: 0;
left: 0;
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="none" stroke="black" stroke-width="2"/></svg>') 12 12, auto;
/* 关键属性:将绘制的区域变为“挖空”模式 */
mix-blend-mode: destination-out;
}
/* 提示文字 */
.hint {
position: absolute;
bottom: -30px;
left: 50%;
transform: translateX(-50%);
color: #666;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="scratch-container">
<!-- 底层内容 -->
<div class="scratch-content">
恭喜你发现了隐藏内容!
</div>
<!-- 上层 Canvas 遮罩层 -->
<canvas id="scratchCanvas"></canvas>
</div>
<p class="hint">用鼠标在灰色区域上擦除</p>
<script>
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('scratchCanvas');
const ctx = canvas.getContext('2d');
const container = document.querySelector('.scratch-container');
// 设置 Canvas 的尺寸
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;
// 擦除半径
const radius = 20;
// 绘制初始遮罩层
function drawInitialMask() {
ctx.fillStyle = '#c0c0c0'; // 银灰色遮罩
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
drawInitialMask();
// 鼠标状态
let isDrawing = false;
// 获取鼠标在 Canvas 上的坐标
function getMousePos(e) {
const rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
// 开始绘制
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
const pos = getMousePos(e);
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fill();
});
// 绘制中
canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
const pos = getMousePos(e);
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fill();
});
// 结束绘制
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
// 鼠标离开 Canvas 区域时也结束绘制
canvas.addEventListener('mouseleave', () => {
isDrawing = false;
});
});
</script>
</body>
</html>
代码解析
-
HTML:
.scratch-container: 一个相对定位的盒子,作为所有内容的舞台。.scratch-content: 绝对定位,覆盖整个容器,这里放了一张背景图片和一行文字,这是被“隐藏”的内容。#scratchCanvas: 绝对定位,也覆盖整个容器,并且位于.scratch-content的上方(HTML 顺序决定了层叠顺序,后写的在上层),它就是我们的“灰色保护膜”。
-
CSS:
(图片来源网络,侵删)position: absolute; top: 0; left: 0;: 确保两个子元素完美重叠。mix-blend-mode: destination-out;: 魔法所在,这个CSS属性定义了元素如何与其背景混合。destination-out的意思是“显示目标(下方元素)的透明部分”,简单说就是让当前元素绘制的部分变成透明。cursor: ...: 我们用内联的 SVG 创建了一个圆形的橡皮擦光标,让用户体验更佳。
-
JavaScript:
- 初始化: 获取
canvas元素和其 2D 绘图上下文ctx,设置canvas的宽高为容器的宽高(这非常重要,否则会模糊)。 drawInitialMask(): 在 Canvas 上填充一个银灰色的矩形,作为初始的遮罩层。- 事件监听:
mousedown: 当鼠标按下时,设置isDrawing为true,并在鼠标位置画一个实心圆,开始“擦除”。mousemove: 当鼠标移动时,isDrawing为true,就在新位置持续画圆,形成一条擦除的轨迹。mouseup和mouseleave: 当鼠标松开或离开 Canvas 区域时,停止绘制。
- 初始化: 获取
高级实现思路与变体
掌握了基本原理后,你可以创造出更多有趣的变体:
使用 globalCompositeOperation 替代 mix-blend-mode
mix-blend-mode 是作用于整个元素的,而 Canvas 的 globalCompositeOperation 是作用于绘图操作的,在 mousemove 事件中设置 ctx.globalCompositeOperation = 'destination-out' 也能达到同样的效果,并且更灵活,可以只对绘图操作生效,不影响元素本身。
// 在 mousedown 或 mousemove 事件中 ctx.globalCompositeOperation = 'destination-out'; // 设置合成操作 ctx.beginPath(); ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2); ctx.fill();
这种方式在逻辑上更纯粹,因为所有操作都在 Canvas 的绘图上下文中完成。
擦除百分比与自动完成
你可以通过计算 Canvas 上透明像素的数量,来计算出已经擦除了多少百分比,当达到某个阈值(如80%)时,自动清除整个遮罩层。
// 在mouseup事件中调用
function checkCompletion() {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
let transparentPixels = 0;
for (let i = 3; i < pixels.length; i += 4) {
if (pixels[i] === 0) { // alpha 通道为 0 表示透明
transparentPixels++;
}
}
const percentage = (transparentPixels / (pixels.length / 4)) * 100;
console.log(`擦除进度: ${percentage.toFixed(2)}%`);
if (percentage > 80) {
// 自动清除所有遮罩
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
改变擦除工具
不一定要用圆形,你可以用方形、星形,甚至是一张图片(比如一个真实的橡皮擦图标)作为擦除工具。
// 使用方形擦除 const brushSize = 40; ctx.fillRect(pos.x - brushSize/2, pos.y - brushSize/2, brushSize, brushSize); // 使用图片擦除 (需要先加载图片) const brushImage = new Image(); brushImage.src = 'brush.png'; // 在 mousedown/mousemove 中 // ctx.drawImage(brushImage, pos.x - brushImage.width/2, pos.y - brushImage.height/2);
擦除粒子效果
当擦除时,不是让像素直接消失,而是让被擦除的像素(比如灰色方块)变成小粒子,然后向上飘散并消失,这需要更复杂的 Canvas 动画,但效果会非常炫酷。
希望这个详细的解释和示例能帮助你理解并创造出属于自己的“鼠标擦除”特效!
