In my work, I sometimes meet some demands for “lottery”. This time, I take “scratch-off project” as an example to share a practical lottery function.

I have modified some H5 scratch-off JS plug-in versions that were circulated on the Internet to make them applicable to actual projects and support the new API of small program Canvas 2D. Incidentally, 2D API is very similar to the actual H5 Canvas JS writing method with only a few differences.

1. Method Introduction:

1.1 Scratch-off JS component

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 || ' '; // Overlay the image
      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; // Scratch off the scale to show all
      this.rpx = wx.getSystemInfoSync().windowWidth / 750; // Device scaling
      this.scale = opts.scale || 0.5;
      this.totalArea = this.width * this.height;
      this.startCallBack = opts.startCallBack || false; // The first scrape triggers the scrape bonus effect
      this.overCallBack = opts.overCallBack || false; // Trigger after scraping
      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) {
         // Check whether it is a network image
         let imgObj = self.canvas.createImage();
         if (self.bgImg.indexOf("http") > -1) {
            await wx.getImageInfo({
               src: self.bgImg, // The address of the image returned by the server
               success: function (res) {
                  imgObj.src = res.path; //res.path is the local address of the network image
               },
               fail: function (res) {
                  // Failed callback
                  console.log(res); }}); }else {
            imgObj.src = self.bgImg; //res.path is the local address of the network image
         }
         imgObj.onload = function (res) {
            self.ctx.drawImage(imgObj, 0.0, self.width * self.rpx, self.height * self.rpx);
            // The method is not executed
         }
         imgObj.onerror = function (res) {
            console.log('onload failure')
            // This method is actually executed}}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
         });
      }

      // Add the calculation of the cleared area, after reaching the standard value, set the card scraper area to scrape clean
      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
Copy the code

1.2 JS call method

new Scratch(self, {
  canvasId: '#coverCanvas'.// Corresponding canvasId
  width: 600.height: 300.//maskColor:"", // cover color
  showPercent: 0.3.// How much scale is scraped off to show the whole area, such as 0.3 is 30% of the area
  bgImg: "./cover.jpg".// Cover image
  overCallBack: () = > {
    // scratch off the callback function
  },
  startCallBack: () = > {
    // Triggers a callback when the user touches the canvas board}})Copy the code

In practice, there are many other configuration items, such as scale, scratch scale, place area and so on, you can set according to the actual needs.

1.3 JS invocation method in actual page:

// Introduce scratch-off section
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:"", // cover color
      bgImg: "./cover.jpg".// Cover image
      overCallBack: () = > {
        this.setData({
          isOver: "It's over."
        })
        //this.clearCanvas();
      },
      startCallBack: () = > {
        this.setData({
          firstTouch: "It's starting to scrape."
        })
        //this.postScratchSubmit();}})},// The scratch card has been scraped clean
  clearCanvas() {
    let self = this;
    console.log("over"); }})Copy the code

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;
}
Copy the code

Note here that type=”2d” is the new 2D canvas.

2. Precautions

  1. Some canvas effects do not support real machine debugging, so just preview them directly
  2. If the scratch result is triggered by the first touch to the canvas, the request needs to write a synchronous method
  3. Scratch-off JS configuration takes precedence over maskColor
  4. You need to scratch it again, you can new it again.

Code snippets

Address: developers.weixin.qq.com/s/RxiaHam57…

It is recommended to upgrade the IDE tool above 1.03.24 to avoid some bugs

Please give me a “like” if you think it’s useful. This is my motivation to continue sharing