小程序刮刮卡

发布于 2024-05-29  165 次阅读


<view class="luck_canvas">
    <view class="luck_info" v-for="(item, index) in cardList" :key="index">
        <view class="luck_num">
            {{item.num}}
        </view>
        <view class="luck_price">
            {{item.gold}}金币
        </view>
    <!-- :style="{'width':'156rpx','height':'156rpx'}" -->
    <!-- :style="{'width':width + 'px','height':height + 'px'}" -->
    </view>
    <canvas class="canvas_box" :style="{'width':'646rpx','height':'312rpx'}" canvas-id="myCanvas" id="myCanvas" @touchstart="touchstart" @touchend="touchend" @touchmove="touchmove">
    </canvas>
</view>
// 参数
canvasCtx: null,
clearPercent: 0,
lastPoint: {},
disabled: false, // 是否禁止刮卡
readyState: false, // 是否开始绘制
endState: false, // 结束刮卡状态

startX: 0, // 触摸x轴位置
startY: 0, // 触摸y轴位置
touchSize: 20, // 触摸画笔大小
percentage: 50, // 刮开百分之多少的时候开奖
scratchList: [{
    num: '08',
    price: '0.01',
    isShow: false
},
{
    num: '25',
    price: '0.01',
    isShow: false
},
{
    num: '30',
    price: '0.01',
    isShow: false
},
{
    num: '19',
    price: '0.01',
     isShow: false
},
{
    num: '01',
    price: '0.01',
    isShow: false
},
{
    num: '08',
    price: '0.01',
    isShow: false
},
{
    num: '55',
    price: '0.01',
    isShow: false
},
{
    num: '39',
    price: '0.01',
    isShow: false
}
],

// 获取画布大小
    getBoxInfo() {
      this.$nextTick(() => {
        let content = uni.createSelectorQuery().select(".luck_canvas");
        content
          .boundingClientRect((data) => {
            console.log(data, "宽高");
            this.width = data.width;
            this.height = data.height;

            setTimeout((e) => {
              const canvasId = "myCanvas";
              this.canvasCtx = uni.createCanvasContext(canvasId, this);
              this.drawMask(this.canvasCtx, data.width, data.height);
              // 页面加载后立即绘制遮挡图层
              // this.scratchList.forEach((item, index) => {
              //    const canvasId = 'myCanvas' + index;
              //    this.ctxMap[canvasId] = uni.createCanvasContext(canvasId,
              //    this);
              //    this.drawMask(this.ctxMap[canvasId], data.width, data.height)
              // });
            }, 20);
          })
          .exec();
      });
    },
    // 绘制遮挡图层
    drawMask(ctx, width, height) {
      // ctx.fillStyle = 'gray';
      // ctx.fillRect(0, 0, width, height);
      // ctx.draw(true);
      // 假设有一个图片路径为 maskImagePath
      const maskImagePath = "/static/glod_mask.png";

      // 绘制图片作为遮挡图层
      ctx.drawImage(maskImagePath, 0, 0, width, height);

      ctx.draw(true);
    },
    // 手指触摸动作开始
    touchstart(e) {
      // if (this.disabled || this.endState) {
      //    return;
      // }

      if (this.cardState == 1) return;
      this.isScroll = false;
      if (e.touches && e.touches[0]) {
        const point = e.touches[0];
        this.lastPoint = point;
      }
      this.startX = e.touches[0].x;
      this.startY = e.touches[0].y;
    },
    // 手指触摸后移动
    touchmove(e) {
      // e.preventDefault();
      // const canvasId = e.currentTarget.id;
      // if (this.disabled || this.endState) {
      //    return;
      // }
      if (this.cardState == 1) return;
      const point = (e.changedTouches || e.touches || [])[0];
      console.log(point, "move移动====");
      if (point) {
        this.refresh(point);
        this.lastPoint = point;
      }
      return;
      const ctx = this.canvasCtx;
      if (ctx) {
        // ctx.clearRect(this.startX, this.startY, this.touchSize, this.touchSize); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
        // ctx.draw(true); // false:本次绘制是否接着上一次绘制,true:保留当前画布上的内容
        ctx.beginPath();
        ctx.arc(this.startX, this.startY, 20, 0, Math.PI * 2);
        ctx.globalCompositeOperation = "destination-out"; // 设置混合模式为擦除模式
        ctx.fill();
        ctx.closePath();
        ctx.draw(true); // false:本次绘制是否接着上一次绘制,true:保留当前画布上的内容
        // this.getFilledPercentage()
      }
      // this[canvasId].clearRect(this.startX, this.startY, this.touchSize, this.touchSize); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
      // this[canvasId].draw(true); // false:本次绘制是否接着上一次绘制,true:保留当前画布上的内容
      // //记录移动点位
      this.startX = e.touches[0].x;
      this.startY = e.touches[0].y;
    },
    // 手指触摸动作结束
    touchend(e) {
      this.isScroll = true;
      if (this.cardState == 1) return;
      if (this.disabled || this.endState) {
        return;
      }
      var point = (e.changedTouches || e.touches || [])[0];

      if (!point) {
        // 没有拿到point直接完成
        this.success();
      }

      this.lastPoint = null;
      if (this.clearPercent > 0.6) {
        this.success();
      }
      return;
      const canvasId = e.currentTarget.id;

      // 返回一个数组,用来描述 canvas 区域隐含的像素数据,在自定义组件下,第二个参数传入自定义组件实例 this,以操作组件内 <canvas> 组件。
      uni.canvasPutImageData(
        {
          canvasId: canvasId,
          x: 0,
          y: 0,
          width: this.width,
          height: this.height,
          success: (res) => {
            console.log(res);
            let pixels = res.data;
            let transPixels = [];
            for (let i = 0; i < pixels.length; i += 4) {
              if (pixels[i + 3] < 128) {
                transPixels.push(pixels[i + 3]);
              }
            }
            var percent = (
              (transPixels.length / (pixels.length / 4)) *
              100
            ).toFixed(2);
            if (percent >= this.percentage) {
              this.success();
            }
          },
          fail: (e) => {
            console.log(e);
          },
        },
        this
      );
    },
    refresh(point) {
      // console.log(pointS,"pointS");
      // var point = pointS.length > 0 && pointS[0] !== undefined ? pointS[0] : {};

      /*
                  小程序 canvas 不支持 globalCompositeOperation 属性
                  this.ctx.globalCompositeOperation = "destination-out"; // 无效
                  所以要很 hack 的根据屏幕滑动始末两端点连成粗线条选区,再自己实现清除选区像素
                */
      var pr = 1;
      var ctx = this.canvasCtx;
      var r = 20 / 2;
      var x1 = this.lastPoint.x;
      var y1 = this.lastPoint.y;
      var x2 = point.x;
      var y2 = point.y;

      // (x1, y1), (x2, y2)分别为线条起始和结尾的两个端点,即粗线条两端点圆弧的圆心 矩形长为手指移动的线条长度,高为线条宽度lineWidth
      // 获取两个点之间的剪辑区域四个端点,即矩形边框顶点(x3, y3)..(x6, y6)
      var asin = r * Math.sin(Math.atan((y2 - y1) / (x2 - x1)));
      var acos = r * Math.cos(Math.atan((y2 - y1) / (x2 - x1)));
      var x3 = x1 + asin;
      var y3 = y1 - acos;
      var x4 = x1 - asin;
      var y4 = y1 + acos;
      var x5 = x2 + asin;
      var y5 = y2 - acos;
      var x6 = x2 - asin;
      var y6 = y2 + acos;

      // 保证线条的连贯,所以在矩形两端画圆
      ctx.save();
      ctx.beginPath();
      ctx.arc(x1, y1, r, 0, 2 * Math.PI);
      ctx.arc(x2, y2, r, 0, 2 * Math.PI);
      ctx.clip();
      ctx.clearRect(0, 0, this.width * pr, this.height * pr);
      ctx.restore();

      // 清除矩形剪辑区域里的像素
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(x3, y3);
      ctx.lineTo(x5, y5);
      ctx.lineTo(x6, y6);
      ctx.lineTo(x4, y4);
      ctx.closePath();
      ctx.clip();
      ctx.clearRect(0, 0, this.width * pr, this.height * pr);
      ctx.restore();

      // 清除线条像素方案2
      // 在小程序内当滑动很快时会导致页面渲染崩溃白屏
      // this._clearCircle(point, r);
      // if (this.lastPoint) {
      //     let posX = point.x - this.lastPoint.x;
      //     let posY = point.y - this.lastPoint.y;
      //     let posXY = Math.abs(posX) + Math.abs(posY);
      //     while(posXY > 6) {
      //         Math.abs(posX) > 3 && (posX += (posX < 0 ? 3 : -3));
      //         Math.abs(posY) > 3 && (posY += (posY < 0 ? 3 : -3));
      //         this._clearCircle({x: point.x - posX, y: point.y - posY}, r);
      //         console.log(this.lastPoint, point, {x: point.x - posX, y: point.y - posY}, posX, posY)
      //         posXY = Math.abs(posX) + Math.abs(posY);
      //     }
      // }
      ctx.draw(true);
      this.calculateClearPercent(x1, y1, x2, y2);
    },
    calculateClearPercent(x1, y1, x2, y2) {
      let area = this.width * this.height;
      console.log(x1, y1, x2, y2, typeof area, area);
      var lx = x2 - x1;
      var ly = y2 - y1;
      var l = Math.sqrt(lx * lx + ly * ly);
      console.log(l, "这是什么==", (l * 20) / area);
      this.clearPercent += (l * 20) / area;
      console.log(this.clearPercent, "this.clearPercent");
    },
    getFilledPercentage() {
      console.log(this.canvasCtx, "this.canvasCtx");
      let imgData = this.canvasCtx.getImageData(0, 0, this.width, this.height); //获取画布中的所有像素
      console.log(imgData, "imgDataimgDataimgData");
      let pixels = imgData.data; //得到像素的字节数据
      let transparent = 0; //设置一个变量来记录已经变为透明的像素点的数量
      for (let i = 0; i < pixels.length; i += 4) {
        let alpha = pixels[i + 3]; //获取每个像素的透明度数值
        if (alpha < 10) transparent++; //当透明度小于10时,认为它已经被擦除,transparent数值加1
      }
      let percentage = transparent / (pixels.length / 4); //计算透明像素在所有像素点中所占比例
      if (percentage > 0.5) {
        // 当像素点的个数超过  60% 时,清空画布,显示底图
        this.canvasCtx.clearRect(0, 0, 250, 100);
      }
    },
    // 成功,清除所有图层
    success(e) {
      if (this.cardState == 1) return;
      this.canvasCtx.moveTo(0, 0); // 把路径移动到画布中的指定点,不创建线条。用 stroke() 方法来画线条。
      this.canvasCtx.clearRect(0, 0, this.width, this.height); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
      // this.canvasCtx.stroke(); // 画出当前路径的边框。默认颜色色为黑色。
      this.canvasCtx.draw(true);
      // this.cardState = 1
      this.scrapingCard();
    },
.luck_canvas {
  border-radius: 16rpx;
  display: flex;
  flex-wrap: wrap;
  position: relative;

  .canvas_box {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 20;
  }

  .luck_info {
    width: 159rpx;
    height: 156rpx;
    background: #fce9c4;
    // border-radius: 16rpx 0rpx 0rpx 0rpx;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border-right: 2rpx dashed #ffe0a3;
    border-bottom: 2rpx dashed #ffe0a3;
    position: relative;

    &:nth-child(n + 5) {
      border-bottom: none !important;
    }

    &:nth-child(4n) {
      border-right: none !important;
    }

    .luck_num {
      font-family: PingFang SC, PingFang SC;
      font-weight: 600;
      font-size: 40rpx;
      // color: #ff9b69;
      // mix-blend-mode: difference;
      /* 文字裁剪属性 */
      /* 将文字颜色设置为透明色 */
      // color: transparent;
      text-transform: uppercase;
      color: transparent;
      -webkit-text-stroke: 1px #ff9b69;
    }

    .luck_price {
      font-family: PingFang SC, PingFang SC;
      font-weight: 400;
      font-size: 24rpx;
      color: #ff5963;
    }
  }
}

只会写bug的bugming