[开盖即食]基于canvas的“刮刮乐”刮奖组件
工作中有时候会遇到一些关于“抽奖”的需求,这次以“刮刮乐项目”举例,分享一个实战抽奖功能。
本人对之前网上流传的一些H5刮刮乐JS插件版本进行了一些改造,使其能适用于实际项目,并且支持小程序canvas 2D的新API,这里顺便提下2D API和实际H5 canvas中JS写法非常类似,只有少数不同。
1、方法介绍:
1.1 刮刮乐JS组件
class Scratch {
constructor(page, opts) {
opts = opts || {};
this.page = page;
this.canvasId = opts.canvasId || 'canvas';
this.width = opts.width || 300;
this.height = opts.height || 300;
this.bgImg = opts.bgImg || ''; //覆盖的图片
this.maskColor = opts.maskColor || '#edce94';
this.size = opts.size || 15,
//this.r = this.size * 2;
this.r = this.size;
this.area = this.r * this.r;
this.showPercent = opts.showPercent || 0.2; //刮开多少比例显示全部
this.rpx = wx.getSystemInfoSync().windowWidth / 750; //设备缩放比例
this.scale = opts.scale || 0.5;
this.totalArea = this.width * this.height;
this.startCallBack = opts.startCallBack || false; //第一次刮时触发刮奖效果
this.overCallBack = opts.overCallBack || false; //刮奖完触发
this.init();
}
init() {
let self = this;
this.show = false;
this.clearPoints = [];
const query = wx.createSelectorQuery();
//console.log(this.canvasId);
query.select(this.canvasId)
.fields({
node: true,
size: true
})
.exec((res) => {
//console.log(res);
this.canvas = res[0].node;
this.ctx = this.canvas.getContext('2d')
this.canvas.width = res[0].width;
this.canvas.height = res[0].height;
//const dpr = wx.getSystemInfoSync().pixelRatio;
//this.canvas.width = res[0].width * dpr;
//this.canvas.height = res[0].height * dpr;
self.drawMask();
self.bindTouch();
})
}
async drawMask() {
let self = this;
if (self.bgImg) {
//判断是否是网络图片
let imgObj = self.canvas.createImage();
if (self.bgImg.indexOf("http") > -1) {
await wx.getImageInfo({
src: self.bgImg, //服务器返回的图片地址
success: function (res) {
imgObj.src = res.path; //res.path是网络图片的本地地址
},
fail: function (res) {
//失败回调
console.log(res);
}
});
} else {
imgObj.src = self.bgImg; //res.path是网络图片的本地地址
}
imgObj.onload = function (res) {
self.ctx.drawImage(imgObj, 0, 0, self.width * self.rpx, self.height * self.rpx);
//方法不执行
}
imgObj.onerror = function (res) {
console.log('onload失败')
//实际执行了此方法
}
} else {
this.ctx.fillStyle = this.maskColor;
this.ctx.fillRect(0, 0, self.width * self.rpx, self.height * self.rpx);
}
//this.ctx.draw();
}
bindTouch() {
this.page.touchStart = (e) => {
this.eraser(e, true);
}
this.page.touchMove = (e) => {
this.eraser(e, false);
}
this.page.touchEnd = (e) => {
if (this.show) {
//this.page.clearCanvas();
if (this.overCallBack) this.overCallBack();
this.ctx.clearRect(0, 0, this.width * this.rpx, this.height * this.rpx);
//this.ctx.draw();
}
}
}
eraser(e, bool) {
let len = this.clearPoints.length;
let count = 0;
let x = e.touches[0].x,
y = e.touches[0].y;
let x1 = x - this.size;
let y1 = y - this.size;
if (bool) {
this.clearPoints.push({
x1: x1,
y1: y1,
x2: x1 + this.r,
y2: y1 + this.r
})
}
for (let item of this.clearPoints) {
if (item.x1 > x || item.y1 > y || item.x2 < x || item.y2 < y) {
count++;
} else {
break;
}
}
if (len === count) {
this.clearPoints.push({
x1: x1,
y1: y1,
x2: x1 + this.r,
y2: y1 + this.r
});
}
//添加计算已清除的面积,达到标准值后,设置刮卡区域刮干净
let clearNum = parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea);
if (!this.show) {
this.page.setData({
clearNum: parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea)
})
};
if (this.startCallBack) this.startCallBack();
//console.log(clearNum)
if (clearNum > this.showPercent) {
//if (len && this.r * this.r * len > this.scale * this.totalArea) {
this.show = true;
}
this.clearArcFun(x, y, this.r, this.ctx);
}
clearArcFun(x, y, r, ctx) {
let stepClear = 1;
clearArc(x, y, r);
function clearArc(x, y, radius) {
let calcWidth = radius - stepClear;
let calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth);
let posX = x - calcWidth;
let posY = y - calcHeight;
let widthX = 2 * calcWidth;
let heightY = 2 * calcHeight;
if (stepClear <= radius) {
ctx.clearRect(posX, posY, widthX, heightY);
stepClear += 1;
clearArc(x, y, radius);
}
}
}
}
export default Scratch
1.2 JS 调用方法
new Scratch(self, {
canvasId: '#coverCanvas', //对应的canvasId
width: 600,
height: 300,
//maskColor:"", //封面颜色
showPercent: 0.3, //刮开多少比例显示全部,比如0.3为 30%面积
bgImg: "./cover.jpg", //封面图片
overCallBack: () => {
//刮奖刮完回调函数
},
startCallBack: () => {
//当用户触摸canvas板的时候触发回调
}
})
实际中还支持其他很多的配置项,比如缩放比例,刮开比例,放置区域等等,大家可以根据实际需求设置。
1.3 实际页面中的JS调用方法:
//引入刮刮乐部分
import Scratch from './scratch.js';
const app = getApp()
Page({
data: {
firstTouch: 0,
isOver: 0,
},
onLoad() {
let self = this;
new Scratch(self, {
canvasId: '#coverCanvas',
width: 600,
height: 300,
//maskColor:"", //封面颜色
bgImg: "./cover.jpg", //封面图片
overCallBack: () => {
this.setData({
isOver: "结束啦"
})
//this.clearCanvas();
},
startCallBack: () => {
this.setData({
firstTouch: "开始刮啦"
})
//this.postScratchSubmit();
}
})
},
//刮卡已刮干净
clearCanvas() {
let self = this;
console.log("over");
},
})
1.4 HTML/CSS
<-- html -->
<view class="wrap">
<canvas class="cover_canvas" type="2d" disable-scroll="false" id='coverCanvas' bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas>
<image class="img" src="reward.jpg" mode="widthFix" />
</view>
/* css */
.wrap {
width: 600rpx;
height: 300rpx;
margin: 100rpx auto;
border: 1px solid #000;
position: relative;
}
.cover_canvas {
width: 600rpx;
height: 300rpx;
z-index: 9;
}
.wrap .img {
position: absolute;
left: 0;
top: 0;
z-index: 1;
width: 600rpx;
height: 300rpx;
}
这里注意 type=“2d” 写法,这里使用的是新的2D canvas。
2、注意事项
- canvas一些效果不支持真机调试,直接预览就行了
- 如果刮奖结果是通过第一次触碰canvas触发的,这里的请求需要写一个同步方法
- 刮刮乐JS的配置会优先判断bgImg这个属性,再判断maskColor
- 需要反复刮奖,可以反复new 它。
3、代码片段
地址: https://developers.weixin.qq.com/s/RxiaHam574or
建议将IDE工具升级到 1.03.24以上,避免一些BUG