微信小程序-滑动拼图安全验证

滑动拼图验证组件

  • [1. 前提介绍](#1. 前提介绍)
    • [2. 最终实现效果图](#2. 最终实现效果图)
    • [3. 封装验证组件并使用](#3. 封装验证组件并使用)
    • [4. 总结](#4. 总结)

1. 前提介绍

本项目是应用taro框架,使用Canvas 画布组件微信开发文档,来实现的

(注:此组件目前是纯前端校验,没涉及后端)

2. 最终实现效果图

3. 封装验证组件并使用

1.编写组件

话不多说,直接上代码

bash 复制代码
view
<template>
  <!-- 滑动验证弹窗 -->
  <view class="mask puzzleVerify-mask" v-if="visible" :catch-move="true">
    <view class="continer_box puzzleVerify-content">
      <!-- 弹窗头部 -->
      <view class="modal-header">
        <text class="modal-title">安全验证</text>
        <view class="modal-close" @tap="onClose">
          <image
            style="width: 100%; height: 100%"
            src="https://yunqi-dy-public.tos-cn-beijing.volces.com/files/20260317/13bb166726b5446fba307934cdb0bf71.png"
          />
        </view>
      </view>
      <view class="puzzleVerify-box">
        <!-- 提示文字 -->
        <view class="puzzle-tip">
          <text class="tip-text">拖动滑块完成拼图验证</text>
        </view>
        <!-- 拼图图片部分 -->
        <view
          class="canvas_img"
          id="canvas_img"
          :style="{ position: 'relative', zIndex: 0 }"
        >
          <!-- 背景图片 -->
          <!-- <canvas
            :style="{
              width: canvas_width + 'px',
              height: canvas_height + 'px',
              border: '1rpx solid transparent',
            }"
            id="firstCanvas"
            type="2d"
          ></canvas> -->
          <canvas id="firstCanvas" type="2d"></canvas>
          <!-- 被抠方块 -->
          <view
            class="canvas_view"
            :style="{
              left: canfile_x + 'px',
              top: canfile_y + 'px',
              position: 'absolute',
              zIndex: 3,
            }"
          ></view>
          <!-- 可移动空格 -->
          <image
            class="canfile_image"
            :style="{
              top: canfile_y + 'px',
              left:
                (slide_clientX > canvas_width - 50
                  ? canvas_width - 50
                  : slide_clientX) + 'px',
              position: 'absolute',
              zIndex: 4,
            }"
            :src="canfile_image"
          />

          <image
            class="refresg-icon"
            src="https://yunqi-dy-public.tos-cn-beijing.volces.com/files/20260317/27a0a2ac09074b62b941f0e1288ed3b7.png"
            @tap="onDrawCanvas"
          />
        </view>
      </view>
      <!-- 滑块 -->
      <view class="canvas-slide">
        <view
          class="canvas-slide-width"
          :style="{
            width: [2, 3].includes(slide_status)
              ? '100%'
              : slide_clientX > canvas_width
              ? canvas_width
              : slide_clientX + 'px',
            background:
              slide_status == 2
                ? '#52CCBA'
                : slide_status == 3
                ? '#F57A7A'
                : '',
          }"
        >
        </view>
        <view
          v-if="slide_status == 0 || slide_status == 1"
          class="canvas-slide-btn fcc"
          @touchstart="onSliderStart"
          @touchmove="onSliderMove"
          @touchend="onSliderEnd"
          :style="{
            left:
              slide_clientX > canvas_width
                ? canvas_width
                : slide_clientX + 'px',
          }"
        >
          <view>→</view>
        </view>
        <view
          class="canvas-slide-tip"
          v-if="slide_status == 0 || slide_status == 1"
          >拖动左边滑块完成上方拼图</view
        >
        <view v-else class="canvas-slide-tip canvas-slide-tip2">
          {{ slide_status == 2 ? "验证成功" : "验证失败,请重试" }}
        </view>
      </view>
    </view>
  </view>
</template>
<script>
import Taro from "@tarojs/taro";
import "./puzzleVerify.scss";
export default {
  props: {
    value: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      visible: false,
      canvas_width: 0,
      canvas_height: 0,
      slidebel: false, //滑动弹窗
      canfile_image: "", //裁剪图片
      canfile_x: "", //被抠方块的水平位置
      canfile_y: "", //被抠方块的垂直位置
      slide_clientX: 0, //移动位置
      slide_status: 0, //0 停止操作   1 触发长按   2 正确   3 错误

      puzzleImages: [
        "https://i.postimg.cc/JDjqt4tw/bg1.jpg",
        "https://i.postimg.cc/3J5HYcPP/bg4.png",
        "https://i.postimg.cc/zBs4DyPQ/bg2.png",
      ],
    };
  },
  watch: {
    value(newVal) {
      this.visible = newVal;
      if (newVal) {
        setTimeout(() => {
          const query = Taro.createSelectorQuery();
          query
            .select("#canvas_img")
            .boundingClientRect((rect) => {
              if (rect) {
                console.log("rect>>", rect);
                this.canvas_width = rect.width;
                this.canvas_height = Math.floor(rect.width * 0.5); // 宽高比 2:1
                setTimeout(() => {
                  this.onDrawCanvas();
                }, 200);
              }
            })
            .exec((rect) => {});
        }, 500);
      }
    },
  },
  methods: {
    onClose() {
      this.$emit("input", false);
    },
    // 画布
    onDrawCanvas(e) {
      var that = this;
      //获取图片条数的随机数
      var imgIndex = Math.floor(Math.random() * this.puzzleImages.length);
      this.canfile_x = Math.round(
        Math.random() * (this.canvas_width - 120) + 60
      );
      this.canfile_y = Math.round(
        Math.random() * ((this.canvas_width * 13) / 28 - 60)
      );
      this.canfile_image = "";
      Taro.createSelectorQuery()
        .select("#firstCanvas") // 在 WXML 中填入的 id
        .fields({ node: true, size: true })
        .exec((res) => {
          // Canvas 对象
          const canvas = res[0].node;
          // 渲染上下文
          const ctx = canvas.getContext("2d");

          // Canvas 画布的实际绘制宽高
          const width = res[0].width;
          const height = res[0].height;

          // 初始化画布大小
          const dpr = wx.getWindowInfo().pixelRatio;
          canvas.width = width * dpr;
          canvas.height = height * dpr;
          ctx.scale(dpr, dpr);

          const image = canvas.createImage();
          // 设置图片src
          image.src = this.puzzleImages[imgIndex];
          // 图片加载完成回调
          image.onload = () => {
            // 将图片完整绘制到 canvas 上
            ctx.drawImage(image, 0, 0, width, height);

            // 绘制完成后,可以添加一些调试信息
            console.log("图片绘制完成,尺寸:", image.width, image.height);
            console.log("Canvas尺寸:", width, height);
          };
          
          //一定要加延时器,不然图片会生成失败
          setTimeout(() => {
            // 生成图片
            Taro.canvasToTempFilePath(
              {
                canvas,
                x: that.canfile_x,
                y: that.canfile_y,
                width: 60,
                height: 60,
                success: (res) => {
                  // 生成的图片临时文件路径
                  that.canfile_image = res.tempFilePath;
                  console.log("tempFilePath>>", that.canfile_image);
                },
              },
              this
            );
          }, 500);
        });
    },
    // 滑动开始
    onSliderStart(e) {
      this.slide_status = 1;
    },
    // 滑动中
    onSliderMove(e) {
      this.slide_clientX =
        e.touches[0].clientX - 30 < 1 ? 0 : e.touches[0].clientX - 30;
    },
    //滑动结束
    onSliderEnd(e) {
      var that = this;
      var cliextX;
      var maxX = this.canvas_width - 30;
      if (that.slide_clientX < 1) {
        that.slide_status = 0;
        return false;
      }
      if (that.slide_clientX > maxX) {
        cliextX = maxX;
      } else {
        cliextX = that.slide_clientX;
      }
      if (that.canfile_x + 5 > cliextX && that.canfile_x - 5 < cliextX) {
        that.slide_status = 2;
        that.slide_clientX = that.canfile_x;
        setTimeout(function () {
          that.slidebel = false;
        }, 500);
        wx.showToast({
          icon: "success",
          title: "验证成功",
        });
        this.$emit("onSuccess");
        this.onClose();
      } else {
        that.slide_status = 3;
      }
      setTimeout(function () {
        that.slide_status = 0;
        that.slide_clientX = 0;
      }, 500);
    },
  },
};
</script>
bash 复制代码
/* 拼图滑动验证 */
.puzzleVerify-mask {
  .puzzleVerify-content {
    position: relative;
    padding: 50rpx 30rpx !important;
    box-sizing: border-box;
  }

  /* 弹窗头部 */
  .modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0rpx 0rpx 30rpx;
    border-bottom: 2rpx solid #f5f5f5;

    .modal-title {
      font-size: 32rpx;
      font-weight: 600;
      color: #333333;
    }

    .modal-close {
      width: 48rpx;
      height: 48rpx;
    }
  }

  .puzzleVerify-box {
    padding-top: 30rpx;
    .puzzle-tip {
      text-align: center;
      margin-bottom: 24rpx;

      .tip-text {
        font-size: 28rpx;
        color: #666666;
      }
    }
    .canvas_img {
      position: relative;
      width: 100%;
      height: 300rpx;
      margin-bottom: 40rpx;
      border-radius: 12rpx;
      overflow: hidden;
      background: #f8f9fa;
    }
    #firstCanvas {
      z-index: 1 !important;
      width: 100%;
      height: 100%;
    }

    /* 被抠的空格 */
    .canvas_view {
      width: 50px;
      height: 50px;
      position: absolute;
      background: rgba(0, 0, 0, 0.6);
      z-index: 2;
      box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.5);
    }
    /* 移动的空格 */
    .canfile_image {
      width: 50px;
      height: 50px;
      position: absolute;
      left: 0;
      z-index: 3;
      box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.8);
      box-sizing: border-box;
    }
    .canfile_image::before {
      content: "";
      position: absolute;
      width: 100%;
      height: 100%;
      box-shadow: 0 0 8px 5px rgba(255, 255, 255, 0.8) inset;
    }

    .refresg-icon {
      position: absolute;
      top: 20rpx;
      right: 20rpx;
      width: 48rpx;
      height: 48rpx;
      z-index: 5;
    }
  }
}

.canvas-slide {
  width: 100%;
  height: 60rpx;
  background: #e5e5e5;
  text-align: center;
  position: relative;
  font-size: 26rpx;
  overflow: hidden;

  /* 滑条上滑块经过的部分 */
  .canvas-slide-width {
    position: absolute;
    left: 0;
    top: 0;
    height: 60rpx;
    background-color: #1991fa;
    width: 0;
    border-top: 1px solid #ddd;
    border-bottom: 1px solid #ddd;
    z-index: 2;
  }
  /* 滑块 */
  .canvas-slide-btn {
    width: 80rpx;
    height: 60rpx;
    background-color: #1991fa;
    font-size: 36rpx;
    font-weight: 700;
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid #ddd;
    color: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
    z-index: 2;
  }

  .canvas-slide-tip {
    width: 100%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    font-size: 24rpx;
    color: #fff;
    transition: color 0.3s ease;
    z-index: 1;
  }
  .canvas-slide-tip.canvas-slide-tip2 {
    z-index: 4;
  }
}

2.引入并使用

bash 复制代码
<template>
 <view>
 <view @tap="onShowVerity">获取验证码</view>
  <puzzleVerify v-model="isShowVerify" @onSuccess="onVerifySuccess" />
 </view>
</template>
<script>
import puzzleVerify from "@/components/puzzleVerify/puzzleVerify.vue";
export default {
  components: {
    puzzleVerify
  },
  data() {
    return {
      isShowVerify: false, //安全校验弹窗
    };
  },
  methods: {
    onVerifySuccess() {
      console.log("验证通过-------------");
    },
    onShowVerity() {
      this.isShowVerify = true;
    },
  }
};
</script>

4. 总结

注:上面的方式是有应用到实际项目当中去的,正常获取流程是没问题的,如果在开发在过程中遇到问题可以发出来一起交流呢

相关推荐
BullSmall2 小时前
第三方软件安全-Dependency‑Track Docker 部署
安全·docker·容器
AImatters3 小时前
国产算力底座,支撑油气储运数字化迈过关键分水岭
安全·cpu·算力·海光·油气储运
叠叠乐6 小时前
中国移动家庭云电脑window关闭所有安全脚本
安全
Jmyd01236 小时前
实训室的 3D 模型涉及肖像文物,数据安全与合规怎么做?
安全·3d·数据安全·虚拟实训
芯盾时代6 小时前
《金融业网络安全管理办法(征求意见稿)》全条款深度拆解(三)
网络·安全·网络安全
敢敢是只喵i7 小时前
一个本地 AI Agent 要操作多个门店或 SaaS 账号,应该怎样安全切换身份?
人工智能·安全·ai·系统架构·业界资讯
Acrel12347 小时前
DC800V 大规模落地,算力机房安全该如何保障
安全
其实防守也摸鱼8 小时前
权限提升与横向移动:从内网渗透到域控的完整技术图谱
运维·服务器·数据库·安全·github·copilot·渗透
深圳海导科技8 小时前
资产防盗追踪系统:物联网高价值资产安全管理|海导科技navynav
科技·物联网·安全
波多利希最高礼仪的握力器8 小时前
安全复制SCP
linux·服务器·安全