Canvas的全局合成操作(globalCompositeOperation),是实现图像遮罩裁剪、自定义形状抠图以及刮刮乐等交互效果的核心技术。它的本质,是精确控制新绘制像素与画布上已有像素的混合方式,这比简单的图层叠加要精细得多,也是实现复杂视觉特效的关键。

理解关键合成模式:destination-out 与 source-over
要实现“挖空”和“刮开”的视觉效果,主要依赖两个核心模式:
- destination-out:这是实现擦除功能的关键。在此模式下,新绘制的内容充当“橡皮擦”,将画布上已有且不透明的像素区域清除为透明。无论是刮刮乐涂层被刮掉,还是从一张图片中裁剪出特定形状,都依赖这个模式。
- source-over:这是Canvas的默认合成模式,即常规的“后绘制的内容覆盖在先绘制的内容之上”。无论是绘制底图,还是铺上一层刮刮乐涂层,通常都使用这个模式。
这里有一个重要细节:globalCompositeOperation是Canvas上下文的一个状态属性。每次需要切换混合模式时,都必须显式地设置它。而且,它只对设置之后发生的绘制操作生效,不会影响之前已经绘制好的内容。
实现图像遮罩裁剪(如圆形头像、自定义形状)
实现思路其实很直观:先把完整的图片绘制到画布上,然后利用destination-out模式,用你想要的形状(比如圆形、多边形路径)去“挖掉”图片上不需要的部分,剩下的就是裁剪后的图像。
以最常见的圆形头像裁剪为例,典型步骤如下:
- 首先,使用
drawImage()将原始图片绘制到Canvas上。 - 接着,将合成模式切换到
destination-out。 - 然后,调用
arc()绘制一个与目标区域等大的圆形,并执行fill()填充。此时,这个圆形覆盖区域的图像像素就会被擦除,变成透明背景,从而呈现出圆形裁剪的效果。 - 最后,如果需要继续绘制其他内容,记得将合成模式重置回
source-over。
⚠️ 实际操作时有一个小坑需要注意:如果图片没有完全覆盖Canvas区域,而你的遮罩路径画到了图片范围之外,那么空白区域也会被“擦除”操作影响。更稳健的做法是采用离屏Canvas:先在另一个不可见的Canvas上完成图片绘制和遮罩裁剪,最后再将处理好的结果一次性drawImage()到主Canvas上。
实现刮刮乐效果(手指/鼠标刮开涂层显示底层内容)
刮刮乐效果的核心在于分层管理。你可以想象成有三层:最底层是奖品图片,中间层是覆盖在上面的不透明涂层(比如灰色半透明色块或纹理),最顶层则是响应交互、负责擦除涂层的逻辑。
典型的实现结构和操作流程如下:
- 准备内容:可以分阶段在同一个Canvas上绘制,也可以使用两个Canvas叠加。首先,使用
drawImage()绘制底层的奖品图。 - 铺设涂层:接着,使用
fillRect()或者带有纹理的drawImage(),在整个Canvas上铺满一层刮刮乐涂层,例如rgba(100, 100, 100, 0.8)。 - 交互擦除:监听
mousedown/touchstart以及mousemove/touchmove事件。在鼠标或手指移动的路径上,连续绘制圆形或线条。 - 关键设置:在绘制这些圆形或线条之前,将
globalCompositeOperation设置为destination-out。这样,你画出的每一个圆(通过arc()和fill())就都变成了橡皮擦,擦除了经过区域的涂层,从而露出底图。 - 体验优化:为了提升刮擦手感的流畅度,可以设置
ctx.lineCap = 'round'和ctx.lineJoin = 'round'让擦除边缘更圆滑,并通过lineWidth控制刮痕的粗细。
更进一步,如果想实现更复杂的擦除边缘效果(比如带纹理的橡皮),可以考虑结合getImageData和putImageData进行像素级操作。不过对于绝大多数刮刮乐场景来说,destination-out模式已经足够高效和实用了。
常见陷阱与优化建议
在开发过程中,下面这几个地方容易出错:
- 状态残留:设置了
destination-out后忘记重置回source-over,导致后续所有绘制都变成擦除操作,画面一片空白。 - 误擦全局:在没有清空画布或没有做好图层隔离的情况下,直接在全局使用
destination-out,可能会把整个画布内容都擦掉。 - 移动端干扰:在移动端处理
touchmove事件时,如果没有调用e.preventDefault()阻止默认行为,页面滚动可能会干扰刮擦操作的连续性。 - 性能问题:
mousemove事件触发频率极高,如果每个点都立刻重绘,可能导致卡顿。建议对绘制操作进行节流(throttle),或者收集一段路径点后批量绘制。
最后分享两个实用小技巧:
- 使用
ctx.sa ve()和ctx.restore()来临时保存和恢复Canvas状态(包括合成模式),这比手动记录和重置要更安全、更简洁。 - 刮刮乐涂层不必总是纯色。你可以用Canvas创建一种带噪点的纹理(
createPattern)来填充,这样刮开时的视觉效果会更逼真,更有质感。
