Canvas实现刮刮乐

思路

  • 使用canvas实现
  • 通过样式控制canvas标签在内容上方
  • 没有刮奖前,使用纯色和文字填充。填充完毕后再显示下面的内容
  • 刮奖过程中使用一个刮奖效果的图片覆盖。防止出现图片读取失败,跨域等问题。这里将图片转成base64再使用
  • 绑定touchstarttouchmovetouchend事件处理刮奖动作
  • PC端绑定mousedownmousemovemouseup事件处理刮奖动作
  • 计算刮奖区域大小,超过总面积的一定比例,就全部打开

实现

  • html部分
<template> <div class="canvas-container"> <canvas v-show="canvasShow" ref="canvas" class="canvas" @mousedown.stop="handleMouseDown" @touchstart.stop="handleMouseDown" @mousemove.stop="handleMouseMove" @touchmove.stop="handleMouseMove" @mouseup.stop="handleMouseUp" @touchend.stop="handleMouseUp" ></canvas> <div v-if="dataShow" class="canvas-m"> <slot></slot> </div> </div> </template>
  • javascript部分
export default { data() { return { isDrawing: false, // 正在绘制中的标识 lastPoint: null, // 记录点的位置 canvas: null, // canvas ctx: null, // context brush: null, // brush canvasShow: true, // canvas是否显示 dataShow: false // 内容是否显示,默认不显示 } }, mounted() { this.initCanvas() }, methods: { initCanvas() { const w = document.documentElement.clientWidth || document.body.clientWidth this.canvasShow = true this.canvas = this.$refs.canvas this.ctx = this.canvas.getContext('2d') // 这里获取 canvas 父标签的宽度和高度,赋值给 canvas const parent = this.canvas.parentElement this.canvas.width = parent.offsetWidth this.canvas.height = parent.offsetHeight // 填充颜色 this.ctx.fillStyle = '#eed6c1' this.ctx.fillRect(0, 0, this.canvas.width + 1, this.canvas.height + 1) // 填充文字 this.ctx.font = 'normal 18px Arial' this.ctx.fillStyle = '#fff' this.ctx.textAlign = 'center' this.ctx.textBaseline = 'middle' this.ctx.fillText('刮刮查看详情', this.canvas.width / 2, this.canvas.height / 2) // 填充好之后,下面的内容可以显示了 this.dataShow = true // brush this.brush = new Image() this.brush.src = `data:image/png;base64,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` }, // 获取坐标点位置 getMouse (e, canvas) { let offsetX = 0 let offsetY = 0 if (canvas.offsetParent !== undefined) { do { offsetX += canvas.offsetLeft offsetY += canvas.offsetTop } while ((canvas = canvas.offsetParent)) } const mx = (e.pageX || e.touches[0].clientX) - offsetX const my = (e.pageY || e.touches[0].clientY) - offsetY return { x: mx, y: my } }, // 获取两个点之间的直线距离 x^2 + y^2 = z^2 distanceBetween (point1, point2) { return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)) }, // 获取两个点之间的偏移角度(弧度) angleBetween (point1, point2) { return Math.atan2(point2.x - point1.x, point2.y - point1.y) }, getFilledInPixels (stride) { if (!stride || stride < 1) { stride = 1 } const pixels = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) const pdata = pixels.data const l = pdata.length const total = (l / stride) let count = 0 // Iterate over all pixels for (let i = count = 0; i < l; i += stride) { if (parseInt(pdata[i]) === 0) { count++ } } return Math.round((count / total) * 100) }, handlePercentage (filledInPixels) { filledInPixels = filledInPixels || 0 if (filledInPixels > 50) { // 超过 50%,隐藏 canvas,让内容完全显示 this.canvasShow = false } }, handleMouseDown (e) { this.isDrawing = true this.lastPoint = this.getMouse(e, this.canvas) }, handleMouseMove (e) { if (!this.isDrawing) { return } // e.preventDefault() const currentPoint = this.getMouse(e, this.canvas) const dist = this.distanceBetween(this.lastPoint, currentPoint) const angle = this.angleBetween(this.lastPoint, currentPoint) let x let y for (let i = 0; i < dist; i++) { x = this.lastPoint.x + (Math.sin(angle) * i) - 25 y = this.lastPoint.y + (Math.cos(angle) * i) - 25 this.ctx.globalCompositeOperation = 'destination-out' this.ctx.drawImage(this.brush, x, y) } this.lastPoint = currentPoint this.handlePercentage(this.getFilledInPixels(32)) }, handleMouseUp (e) { this.isDrawing = false } } }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: