Skip to content

刮刮乐

刮刮乐是一种比较常见的抽奖游戏形式。

原理

实现原理是用一个 canvas 遮盖在奖品图片(或文字)上面,canvas 的内容可以是一个纯色的矩形,也可以是一张图片。当用户用手去滑动(touchmove 事件)canvas 的内容时,计算一个清除的区域,然后调用ctx.clearRect()方法清除 canvas 中内容。因为 canvas 默认是透明的,这样就会显示出底下的奖品图。

试一试

试一试

示例代码

html:

html
<div class="item">
  <canvas ref="canvas" class="canvas" width="200" height="146" @mousedown="touchstart" @mousemove="touchmove" @mouseup="touchend"></canvas>
  <img class="jackpot" src="./images/jackpot.png" alt="" />
</div>
注意:如果是移动端,需要绑定touch事件。
html
<div class="item">
  <canvas ref="canvas" class="canvas" width="200" height="146" @mousedown="touchstart" @mousemove="touchmove" @mouseup="touchend"></canvas> 
  <canvas ref="canvas" class="canvas" width="200" height="146" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas> 
  <img class="jackpot" src="./images/jackpot.png" alt="" />
</div>

js:

js
import { ref, onMounted, computed } from 'vue'
const canvas = ref()
const isClearImg = ref(false)
const ctx = computed(() => canvas.value.getContext('2d', { willReadFrequently: true }))
onMounted(() => {
  ctx.value.fillStyle = '#cccccc'
  ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height)
})
const clearArcFun = (x, y, r) => {
  const PIR = Math.round(Math.PI * r)
  for (let i = 0; i < PIR; i++) {
    const angle = (i / PIR) * 360
    ctx.value.clearRect(x, y, Math.sin(angle * (Math.PI / 180)) * r, Math.cos(angle * (Math.PI / 180)) * r)
  }
}
const touchstart = () => {
  isClearImg.value = true
}
const touchmove = (e) => {
  const { offsetX, offsetY } = e
  if (isClearImg.value) {
    const size = 20
    clearArcFun(offsetX, offsetY, size)
  }
}
const touchend = () => {
  isClearImg.value = false
  let sum = 0
  const { data = [] } = ctx.value.getImageData(0, 0, canvas.value.width, canvas.value.height)
  for (let i = 0; i < data.length; i += 4) {
    if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0 && data[i + 3] === 0) {
      sum++
    }
  }
  if ((4 * sum) / data.length >= 50 / 100) {
    ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height)
  }
}
注意:使用图片的写法。
js
onMounted(async () => {
  const img = new Image()
  await new Promise((resolve) => {
    img.onload = resolve
    img.src = './images/guaguale-bg.png' // 要加载的图片 url
  })
  ctx.value.drawImage(img, 0, 0, canvas.value.width, canvas.value.height)
})
注意:如果是移动端,touchmove中获取clientX, clientY的方式有变化。如果容器有偏移量,比如left:100px,top:100px。则clearArcFun中需要减去偏移量。
js
const touchmove = (e) => {
  const { clientX, clientY } = e 
  const { clientX, clientY } = e && e.touches && e.touches[0] 
  if (isClearImg.value) {
    const size = 20
    clearArcFun(clientX, clientY, size) 
    clearArcFun(clientX - 100, clientY - 100, size) 
  }
}

clearArcFun 方法

示例代码

js
const clearArcFun = (x, y, r) => {
  const PIR = Math.round(Math.PI * r)
  for (let i = 0; i < PIR; i++) {
    const angle = (i / PIR) * 360
    ctx.value.clearRect(x, y, Math.sin(angle * (Math.PI / 180)) * r, Math.cos(angle * (Math.PI / 180)) * r)
  }
}

每一次 touchmove 时都会执行clearArcFun方法。该方法接收三个参数
x:touchmove 时点的 x 坐标
y:touchmove 时点的 y 坐标
r:半径
x,y作为ctx.clearRect()方法的前两个参数,并根据r计算出每一个矩形的widthheight。最终每一次 touchmove 清除的区域都是一个由许多矩形组成的圆形区域。

提示:

以鼠标点击的那一点为坐标原点,ctx.clearRect()方法的第 3 个参数width如果是负数,则清除的矩形区域在第 2(height 也为负数)、3 象限。ctx.clearRect()方法的第 4 个参数height如果是负数,则清除的矩形区域在第 1(height 也为负数)、4 象限。

下图演示了一次 touchmove clearArcFun方法执行的过程: 可以看出来,其实是清除了一个又一个的矩形,最终形成一个圆形区域。