You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用JavaScript的canvas元素实现刮开显示图片的效果?

基于Canvas实现完整刮开显示图片功能的实现方案

你已经实现的路径绘制逻辑可以直接复用,只需要调整canvas的合成模式、加上层级结构和可选的完成判定逻辑即可。

核心实现逻辑

整体采用双层叠加结构:

  • 底层是需要刮开后显示的目标图片
  • 上层是覆盖在图片上方的canvas元素,默认填充遮罩色/遮罩图片
  • 通过globalCompositeOperation = 'destination-out'配置,让用户绘制的路径变成透明区域,露出底层图片

步骤实现

1. 基础HTML结构

<div class="scratch-container">
  <!-- 底层要显示的目标图片 -->
  <img src="目标图片地址" class="target-img" alt="刮开显示的内容">
  <!-- 上层刮刮层canvas -->
  <canvas id="scratchCanvas"></canvas>
</div>

2. 基础CSS样式

.scratch-container {
  position: relative;
  width: 300px;
  height: 300px;
  /* 禁止用户选中内容避免干扰操作 */
  user-select: none;
}
.target-img, #scratchCanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
#scratchCanvas {
  /* 保证canvas在最上层接收交互事件 */
  z-index: 10;
  cursor: pointer;
}

3. JS核心逻辑实现

const canvas = document.getElementById('scratchCanvas');
const ctx = canvas.getContext('2d');
const container = document.querySelector('.scratch-container');
// 标记是否正在刮动
let isScratching = false;
// 刮开自动完成的阈值,可自定义
const FINISH_THRESHOLD = 0.6;

// 初始化canvas尺寸和容器一致
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;

// 初始化遮罩层,可填充纯色或者绘制遮罩图片
ctx.fillStyle = '#cccccc'; // 遮罩层颜色,也可以换成图片
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 核心配置:新绘制的内容会清除已有区域,实现透明效果
ctx.globalCompositeOperation = 'destination-out';
// 擦除路径样式配置
ctx.lineWidth = 30; // 擦除的笔触宽度
ctx.lineCap = 'round'; // 圆形笔触让边缘更平滑
ctx.lineJoin = 'round';

// 坐标转换工具:把页面坐标转换成canvas内部坐标
function getCanvasPos(e) {
  const rect = canvas.getBoundingClientRect();
  const clientX = e.clientX || (e.touches && e.touches[0].clientX);
  const clientY = e.clientY || (e.touches && e.touches[0].clientY);
  return {
    x: clientX - rect.left,
    y: clientY - rect.top
  }
}

// 开始刮动
function startScratch(e) {
  isScratching = true;
  const pos = getCanvasPos(e);
  ctx.beginPath();
  ctx.moveTo(pos.x, pos.y);
  // 阻止移动端默认滚动行为
  e.preventDefault();
}

// 刮动中
function scratchMove(e) {
  if (!isScratching) return;
  const pos = getCanvasPos(e);
  ctx.lineTo(pos.x, pos.y);
  ctx.stroke();
  e.preventDefault();
  // 可选:实时计算刮开比例,判断是否完成
  checkFinish();
}

// 结束刮动
function endScratch() {
  isScratching = false;
}

// 检查是否刮开比例达到阈值
function checkFinish() {
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  let transparentPixels = 0;
  // 每4个值对应一个像素的rgba
  for (let i = 3; i < pixels.length; i += 4) {
    if (pixels[i] === 0) transparentPixels++;
  }
  const ratio = transparentPixels / (canvas.width * canvas.height);
  if (ratio >= FINISH_THRESHOLD) {
    // 达到阈值直接清空整个canvas,完成刮开效果
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // 移除事件监听避免重复触发
    canvas.removeEventListener('mousedown', startScratch);
    canvas.removeEventListener('touchstart', startScratch);
    // 这里可以加自定义完成后的回调逻辑
  }
}

// 绑定桌面端事件
canvas.addEventListener('mousedown', startScratch);
window.addEventListener('mousemove', scratchMove);
window.addEventListener('mouseup', endScratch);
canvas.addEventListener('mouseout', endScratch);

// 绑定移动端事件
canvas.addEventListener('touchstart', startScratch);
window.addEventListener('touchmove', scratchMove);
window.addEventListener('touchend', endScratch);

注意事项

  • 如果需要用图片作为遮罩层,需要等遮罩图片加载完成后再调用ctx.drawImage()绘制到canvas上,避免出现空白
  • 如果需要计算刮开比例,涉及到getImageData调用,要保证所有用到的图片都和页面同域,或者配置了CORS,否则会触发跨域报错
  • 擦除的笔触宽度、自动完成的阈值都可以根据需求自行调整

内容的提问来源于stack exchange,提问作者Jeremy Lanssiers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 21:30:00