如何用 canvas 实现鼠标橡皮擦功能?

FSD-燕丽 阅读 4

我在做一个简单的画板,想加个橡皮擦功能,但试了几次都不对。我用的是 canvas,正常画画没问题,但切换到橡皮擦模式后,不是把整个画布清空,就是根本擦不掉内容。是不是 globalCompositeOperation 的用法不对?

这是我的基础结构:

<canvas id="board" width="800" height="600"></canvas>
<button id="eraserBtn">橡皮擦</button>
<script>
  const ctx = board.getContext('2d');
  // 画线逻辑省略...
  eraserBtn.addEventListener('click', () => {
    ctx.globalCompositeOperation = 'destination-out';
  });
</script>

点完橡皮擦按钮后,再拖动鼠标,要么没反应,要么一擦就全没了,到底该怎么正确实现局部擦除?

我来解答 赞 1 收藏
二维码
手机扫码查看
1 条解答
A. 丹丹
A. 丹丹 Lv1
你这思路是对的,destination-out 确实是做橡皮擦的正确姿势。问题应该出在绘制逻辑上,光设置属性不够,你还得真正"画"出擦除的路径才行。

说几个常见的坑:

第一,destination-out 模式下,你画的形状会把已有的像素"挖掉",但前提是你得真的画出来。很多人设置了属性,但没调用 stroke()fill(),那当然没反应。

第二,每次绘制前记得 beginPath(),不然路径会累积,一画就把之前所有的路径都重绘一遍,直接清空一大片。

直接上代码吧,这样更清楚:

const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
const eraserBtn = document.getElementById('eraserBtn');

let isDrawing = false;
let mode = 'draw'; // 'draw' 或 'eraser'

// 初始化画笔样式
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 5;
ctx.strokeStyle = '#333';

// 鼠标事件
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
});

canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;

ctx.lineTo(e.offsetX, e.offsetY);

if (mode === 'eraser') {
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 20; // 橡皮擦大一点
ctx.stroke();
} else {
ctx.globalCompositeOperation = 'source-over';
ctx.lineWidth = 5;
ctx.stroke();
}
});

canvas.addEventListener('mouseup', () => {
isDrawing = false;
});

canvas.addEventListener('mouseleave', () => {
isDrawing = false;
});

// 切换橡皮擦模式
eraserBtn.addEventListener('click', () => {
mode = mode === 'draw' ? 'eraser' : 'draw';
eraserBtn.textContent = mode === 'eraser' ? '画笔' : '橡皮擦';
});


核心逻辑就是:橡皮擦模式下,用 destination-out 配合 stroke() 去画路径,画到哪擦到哪。

再补充一点,如果你的画板有背景图或者背景色,destination-out 会把那块区域变成完全透明,露出 canvas 底层的东西。如果想要"擦除后露出白色背景"的效果,得用其他方案,比如在底层铺一个白色 canvas,或者干脆用白色线条去覆盖。这个看你的具体需求了。
点赞
2026-03-02 15:22