评论

[开盖即食]基于canvas的“刮刮乐”刮奖组件

刮刮乐实战项目,对网上流传的JS组件进行了优化改进

工作中有时候会遇到一些关于“抽奖”的需求,这次以“刮刮乐项目”举例,分享一个实战抽奖功能。

本人对之前网上流传的一些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、注意事项

  1. canvas一些效果不支持真机调试,直接预览就行了
  2. 如果刮奖结果是通过第一次触碰canvas触发的,这里的请求需要写一个同步方法
  3. 刮刮乐JS的配置会优先判断bgImg这个属性,再判断maskColor
  4. 需要反复刮奖,可以反复new 它。

3、代码片段

地址: https://developers.weixin.qq.com/s/RxiaHam574or

建议将IDE工具升级到 1.03.24以上,避免一些BUG

觉得有用,请点个赞,这是我继续分享的动力~

最后一次编辑于  2021-02-18  
点赞 15
收藏
评论

10 个评论

  • 满目山河
    满目山河
    2021-09-10

    请问一个页面有两个 第二个不能画怎么解决 canvasid不一样

    2021-09-10
    赞同
    回复
  • 清渡失眠
    清渡失眠
    2021-09-09

    canvas 斜体无效(新版旧版都无效)怎么解决?


    2021-09-09
    赞同
    回复 1
    • 圣殿骑士
      圣殿骑士
      2021-09-09
      截止20210909 这个问题无解~
      2021-09-09
      回复
  •  南风 .
     南风 .
    2021-05-07

    six

    2021-05-07
    赞同
    回复
  • 润立仿石漆(孔老八)18977098445
    润立仿石漆(孔老八)18977098445
    2021-04-12
    义氵
    2021-04-12
    赞同
    回复
  • 润立仿石漆(孔老八)18977098445
    润立仿石漆(孔老八)18977098445
    2021-04-12
    l。
    2021-04-12
    赞同
    回复
  • Sara 柏月
    Sara 柏月
    2021-02-26
    厉害,已阅,已阅
    2021-02-26
    赞同
    回复
  • 下渝州
    下渝州
    2021-02-18

    厉害

    2021-02-18
    赞同
    回复
  • brave
    brave
    2021-02-18

    已阅,已赞,已收藏

    2021-02-18
    赞同
    回复
  • 吴奕群
    吴奕群
    2021-02-18

    已阅,已赞,已收藏

    2021-02-18
    赞同
    回复
  • 拾忆
    拾忆
    发表于小程序端
    2021-02-18
    已阅
    2021-02-18
    赞同
    回复
登录 后发表内容