刮刮乐
刮刮乐是一种比较常见的抽奖游戏形式。
原理
实现原理是用一个 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
计算出每一个矩形的width
和height
。最终每一次 touchmove 清除的区域都是一个由许多矩形组成的圆形区域。
提示:
以鼠标点击的那一点为坐标原点,ctx.clearRect()
方法的第 3 个参数width
如果是负数,则清除的矩形区域在第 2(height 也为负数)、3 象限。ctx.clearRect()
方法的第 4 个参数height
如果是负数,则清除的矩形区域在第 1(height 也为负数)、4 象限。
下图演示了一次 touchmove clearArcFun
方法执行的过程: 可以看出来,其实是清除了一个又一个的矩形,最终形成一个圆形区域。