VUE 实现滑块验证 ①

这篇具有很好参考价值的文章主要介绍了VUE 实现滑块验证 ①。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

VUE 实现滑块验证 ①

@作者 : SYFStrive

 
VUE 实现滑块验证 ①

@博客首页 : HomePage

📜: 微信小程序

📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗

📌:觉得文章不错可以点点关注 👉:专栏连接🔗

💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞

VUE 实现滑块验证 ①
VUE 实现滑块验证 ①
VUE 实现滑块验证 ①

👉 VUE专栏(🔥)
                    ⡖⠒⠒⠒⠤⢄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸   ⠀⠀⠀⡼⠀⠀⠀⠀ ⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢶⣲⡴⣗⣲⡦⢤⡏⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠋⠉⠉⠓⠛⠿⢷⣶⣦⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠇⠀⠀⠀⠀⠀⠀⠘⡇⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀⠀⠀⠀⢰⠇⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⡴⠊⠉⠳⡄⠀⢀⣀⣀⡀⠀⣸⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀⠰⠆⣿⡞⠉⠀⠀⠉⠲⡏⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⢧⡀⣀⡴⠛⡇⠀⠈⠃⠀⠀⡗⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣱⠃⡴⠙⠢⠤⣀⠤⡾⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⡇⣇⡼⠁⠀⠀⠀⠀⢰⠃⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣸⢠⣉⣀⡴⠙⠀⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⡏⠀⠈⠁⠀⠀⠀⠀⢀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣀⠤⠚⣶⡀⢠⠄⡰⠃⣠⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢀⣠⠔⣋⣷⣠⡞⠀⠉⠙⠛⠋⢩⡀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀
⠀⡏⢴⠋⠁⠀⣸⠁⠀⠀⠀⠀⠀ ⠀⣹⢦⣶⡛⠳⣄⠀⠀⠀⠀⠀
⠀⠙⣌⠳⣄⠀⡇   不能   ⡏⠀⠀  ⠈⠳⡌⣦⠀⠀⠀⠀
⠀⠀⠈⢳⣈⣻⡇   白嫖 ⢰⣇⣀⡠⠴⢊⡡⠋⠀⠀⠀⠀
⠀⠀⠀⠀⠳⢿⡇⠀⠀⠀⠀⠀⠀⢸⣻⣶⡶⠊⠁⠀⠀
⠀⠀⠀⠀⠀⢠⠟⠙⠓⠒⠒⠒⠒⢾⡛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⠏⠀⣸⠏⠉⠉⠳⣄⠀⠙⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⡰⠃⠀⡴⠃⠀⠀⠀⠀⠈⢦⡀⠈⠳⡄⠀⠀⠀⠀⠀⠀⠀
⠀⠀⣸⠳⣤⠎⠀⠀⠀⠀⠀⠀⠀⠀⠙⢄⡤⢯⡀⠀⠀⠀⠀⠀⠀
⠀⠐⡇⠸⡅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⡆⢳⠀⠀⠀⠀⠀⠀
⠀⠀⠹⡄⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣇⠸⡆⠀⠀⠀⠀⠀
⠀⠀⠀⠹⡄⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡀⣧⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢹⡤⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣷⠚⣆⠀⠀⠀⠀
⠀⠀⠀⡠⠊⠉⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡎⠉⠀⠙⢦⡀⠀
⠀⠀⠾⠤⠤⠶⠒⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠒⠲⠤⠽   

提示:以下是本篇文章正文内容

V u e j s Vuejs Vuejs


简介 : Vue 是一套用于构建用户界面的 渐进式 框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

Canvas参考链接 :https://blog.csdn.net/u01246837

滑块图示

VUE 实现滑块验证 ①

VUE 实现滑块验证 ①

结构框架

  Html 结构

 <!--滑块验证模块包裹-->
  <div class="slide-authCode-wrap" v-show="isOpen">
    <!--底下小箭头-->
    <div class="arrow"></div>
    <!--关闭按钮-->
    <div class="close" @click="Close">
      <span class="iconfont icon-chacha"></span>
    </div>

    <!--滑块主要容器-->
    <div class="validate-wrap">
      <!--header 头部部分-->
      <div class="refresh">
        <div class="refresh-text">完成拼图验证</div>
        <!--刷新数据按钮-->
        <div class="refresh-icon" @click="Refresh">
          <!--刷新按钮Icon-->
          <span class="icon iconfont icon-gengxin" ref="iconRotate"></span>
          <span>换一张</span>
        </div>
      </div>
      <!--滑块区域-->
      <div class="slider-main-container">
        <!-- 画布容器Box -->
        <div id="captcha" ref="captcha" style="position: relative">
          <!-- 画布bg -->
          <canvas ref="canvas_bg" width="364" height="142"
          >浏览器版本过低,请升级浏览器
          </canvas
          >
          <!-- 滑块box -->
          <canvas ref="blockBox" width="364" height="142" class="block"
          >浏览器版本过低,请升级浏览器
          </canvas
          >
          <!--用来加载图片标签 不显示-->
          <img ref="img" src="" style="display: none" width="0" height="0"/>
          <!-- <img
            ref="img"
            src="./images/722-300x150.jpg"
            width="0"
            height="0"
            style="display: none"
          /> -->
          <!-- 拖动容器Box -->
          <div
              class="slider-container"
              :class="slideVerifyStatus === 4 ? 'slider-container-fail' : ''"
          >
            <div class="slide-bg">
              <div class="left"></div>
              <div class="center">拖动滑块完成拼图,进行账号验证</div>
              <div class="right"></div>
            </div>
            <!-- 拖动遮罩 -->
            <div ref="slider_mask" class="slider-mask">
              <!-- 拖动块 -->
              <div
                  ref="slider"
                  class="slider"
                  @mousedown="SliderMousedownEvent"
              >
                <!-- 拖动Icon -->
                <span
                    class="slider-icon iconfont"
                    :class="[
                    slideVerifyStatus === 0 && 'icon-tubiao-xiaoshou',
                    slideVerifyStatus === 2 && 'icon-gouxuan',
                    slideVerifyStatus === 4 && 'icon-close',
                  ]"
                >
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  Css 结构


<style scoped lang="less">
//滑块验证
.slide-authCode-wrap {
  position: absolute;
  left: 0;
  z-index: 110;
  bottom: 65px;
  width: 364px;
  height: 216.5px;
  padding: 12px 12px 12px 20px;
  border: 1px solid #eee;
  box-shadow: 0 0 2px 2px #eee;
  background-color: #fff;

  //关闭验证
  .close {
    cursor: pointer;
    z-index: 100;
    position: absolute;
    right: 10px;
    top: 10px;
    display: block;
    width: 20px;
    height: 20px;
    line-height: 20px;

    span {
      font-size: 20px;

      &:active {
        color: #a4a4a4;
      }
    }
  }

  //箭头
  .arrow {
    display: block;
    position: absolute;
    background-image: url("./images/tips.gif");
    background-repeat: no-repeat;
    width: 16px;
    height: 8px;
    background-position: 0 -8px;
    overflow: hidden;
    bottom: -8px;
    left: 190px;
  }

  //滑块主要容器
  .validate-wrap {
    //提醒区域
    .refresh {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-right: 30px;
      font-family: Helvetica, Tahoma, Arial, "Microsoft YaHei", "微软雅黑",
      sans-serif;

      .refresh-text {
        font-size: 15px;
        color: #666;
      }

      .refresh-icon {
        cursor: pointer;
        color: #06c;

        .icon {
          display: inline-block;
          margin-right: 4px;
          vertical-align: revert;
          transition: 0.6s linear;
        }

        span {
          font-size: 15px;
        }
      }
    }

    //滑块组要容器
    .slider-main-container {
      margin-top: 5px;
      //画布容器Box
      #captcha {
        display: flex;
        justify-content: center;
        flex-direction: column;
        /* 小拼图 */

        .block {
          position: absolute;
          left: 0;
          top: 0;
        }

        /* 滑动条 */

        .slider-container {
          position: relative;
          margin: 10px auto 0;
          opacity: 1;
          font-size: 14px;
          visibility: visible;
          width: 364px;
          height: 40px;
          line-height: 40px;
          text-align: center;
          color: #05a4ea;

          //滑块Bg
          .slide-bg {
            .left {
              float: left;
              width: 40px;
              height: 40px;
              background: url("./images/slide-left-icon2.png") no-repeat;
            }

            .center {
              background-image: url("./images/slide-center-bg.png");
              margin-left: 40px;
              margin-right: 40px;
              overflow: hidden;
              white-space: nowrap;
              user-select: none;
              -moz-user-select: none;
              -ms-user-select: none;
              -webkit-user-select: none;
            }

            .right {
              width: 40px;
              height: 40px;
              background: url("./images/slide-right-icon2.png") no-repeat;
              position: absolute;
              right: 0;
              top: 0;
            }
          }

          /* 拖动遮罩容器 */

          .slider-mask {
            position: absolute;
            top: 0;
            left: 0;
            width: 364px;
            height: 40px;
            border-radius: 36px;
            border: 0px solid #1991fa;
            background: linear-gradient(#33b5fb, #8fdfff);
          }

          /* 拖动块 */

          .slider {
            position: absolute;
            left: -3px;
            top: -3px;
            width: 45px;
            height: 45px;
            background: #fff;
            box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
            cursor: pointer;
            transition: background 0.2s linear;
            border-radius: 50%;
          }

          .slider:hover {
            background: #aed6ff;
            color: #06c;
          }

          /* 拖动Icon */

          .slider-icon {
            font-size: 25px;
            font-weight: 700;
            vertical-align: middle;
          }
        }

        //活动状态CSS

        /* 滑动条失败态 */

        .slider-container-fail {
          .slider-mask {
            background: linear-gradient(#ff5e5e, #ffb3b3);
          }

          .slider {
            padding-top: 2px;
            box-sizing: border-box;
          }

          .slider-icon {
            color: red;
          }
        }
      }
    }
  }
}
</style>

  JS 结构

<script>
import {
  computed,
  getCurrentInstance,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  toRefs,
} from "vue";
//第三方模块
import throttle from "lodash/throttle"; // 引入节流会防抖插件
import { ElMessage } from "element-plus";
//自定义模块
import { mapActionsFun } from "@/hooks/VueX";
//获取两个值之间的随机数
import { GetRandomNumberByRange } from "@/utils/Random";
// //回去两个值之间的随机数
// function GetRandomNumberByRange(start, end) {
//     return Math.round(Math.random() * (end - start) + start);
// }
import GetGlobalData from "@/utils/Global/GetGlobalData";

export default {
  name: "SliderVerification",
  emits: ["successCallback", "failCallback"],
  setup(props, context) {
    const data = reactive({
      ll: 0,
      slideVerifyStatus: 0, //控制滑块的状态Icon
      isOpen: false,
    });

    const slidingData = {
      l: 35, //去掉突出的部分滑块总边长
      r: 7.5, //滑块突出的小圆圈半径
      w: 364, //canvas宽度
      h: 142, //canvas高度
      PI: Math.PI, //2PI = 360
      ll: 0, //滑块的实际边长(包括突出部分)
    };
    data.ll = computed(() => {
      return slidingData.l + slidingData.r * 2; //滑块的实际边长(包括突出部分)
    });
    const thisData = {
      x: 0,
      y: 0,
      captcha: null,
      canvas_bg: null,
      blockBox: null,
      img: null,
      slider_mask: null,
      slider: null,
      url: "",
      iconRotate: null,
      currentGlobalData: null,
    };
    const ctx = {
      canvasCtx: null,
      blockBoxCtx: null,
    };
    const methods = {
      Refresh: null,
      close: null,
      SliderMousedownEvent: null,
    };
    const eventData = {
      originX: 0,
      originY: 0,
      trail: [],
      isMouseDown: false,
    };

    //#region 生命周期
    const currentInstance = getCurrentInstance();
    data.currentGlobalData = GetGlobalData();
    onMounted(async () => {
      await GetElement();
      await EventGlobal();
    });

    onBeforeUnmount(() => {
      data.currentGlobalData.$bus.all.delete("openSlideVerify");
    });

    //#endregion

    //#region 封装方法
    const GetData = throttle(async () => {
      try {
        // Ajax请求 获取图片
        thisData.url = (
          await mapActionsFun(["getVerifySlideImg"]).getVerifySlideImg()
        ).url;
        thisData.img.src = thisData.url;
        await DrawInitialize();
        await InitImg();
      } catch (e) {
        ElMessage({
          showClose: true,
          dangerouslyUseHTMLString: true,
          type: "error",
          message: e,
          duration: 1500,
        });
      }
    }, 1500);

    // 获取需要的元素
    const GetElement = async () => {
      thisData.captcha = currentInstance.proxy.$refs.captcha;
      thisData.canvas_bg = currentInstance.proxy.$refs.canvas_bg;
      thisData.blockBox = currentInstance.proxy.$refs.blockBox;
      thisData.img = currentInstance.proxy.$refs.img;
      thisData.slider_mask = currentInstance.proxy.$refs.slider_mask;
      thisData.slider = currentInstance.proxy.$refs.slider;
      thisData.iconRotate = currentInstance.proxy.$refs.iconRotate;
      // alpha(boolean):表示canvas是否包含一个alpha通道,设为false则浏览器知道背景永远不透明,能加速对于透明场景和图像的绘制。
      // willReadFrequently(Boolean):表示是否计划有大量的回读操作,频繁调用getImageData()方法时能节省内存,仅Gecko内核浏览器支持。
      // storage(String):声明使用的storage类型,默认为”persistent”。
      ctx.canvasCtx = thisData.canvas_bg.getContext("2d", {
        willReadFrequently: true,
      });
      ctx.blockBoxCtx = thisData.blockBox.getContext("2d", {
        willReadFrequently: true,
      });
    };
    // 绘画方法
    const DrawInitialize = () => {
      thisData.x = GetRandomNumberByRange(
        data.ll + 10,
        slidingData.w - (data.ll + 10)
      );
      thisData.y = GetRandomNumberByRange(
        slidingData.r * 2 + 10,
        slidingData.h - (data.ll + 10)
      );
      // fill 通过填充路径的内容区域生成实心的图形
      // clip 把已经创建的路径转换成裁剪路径。裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。
      // 注意:clip()只能遮罩在这个方法调用之后绘制的图像,如果是clip()方法调用之前绘制的图像,则无法实现遮罩。
      Draw(ctx.canvasCtx, "fill", thisData.x, thisData.y);
      Draw(ctx.blockBoxCtx, "clip", thisData.x, thisData.y);
    };

    // 绘制  2D渲染,渲染方式,坐标(X,Y)
    function Draw(ctx, operation, x, y) {
      // ★前提要创建Canvas 否者无法绘画
      // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径
      ctx.beginPath();
      // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。
      ctx.moveTo(x, y);
      //(绘制一条从当前位置到指定坐标x,y)的直线. 假如 x 60~304 y 25~82
      ctx.lineTo(x + slidingData.l / 2, y);
      // 绘制圆弧 x, y, r, startAngle, endAngle, anticlockwise 六个参数
      // 以(x, y)为圆心,以r为半径,从 startAngle弧度开始到endAngle弧度结束。anticlosewise是布尔值,true表示逆时针,false表示顺时针。(默认是顺时针)
      // 注意1 这里的度数都是弧度 👉 0弧度是指的x轴正方形
      // 注意2 arc绘制的坐标是从最开始的位置计算的
      ctx.arc(
        x + slidingData.l / 2,
        y - slidingData.r,
        slidingData.r,
        0,
        2 * slidingData.PI
      );
      // 回到原来起步画圆的位置
      ctx.lineTo(x + slidingData.l / 2, y);
      // 半径 直径是指 L
      // 向右再走 直径 位置
      ctx.lineTo(x + slidingData.l, y);
      // 向右再走 半径 位置
      ctx.lineTo(x + slidingData.l, y + slidingData.l / 2);
      // arc方法 须从最开始的位置计算 走到最右边 再向右走半径位置 向下走半径位置
      ctx.arc(
        x + slidingData.l + slidingData.r,
        y + slidingData.l / 2,
        slidingData.r,
        0,
        2 * slidingData.PI
      );
      ctx.lineTo(x + slidingData.l, y + slidingData.l / 2);
      ctx.lineTo(x + slidingData.l, y + slidingData.l);
      ctx.lineTo(x, y + slidingData.l);
      ctx.lineTo(x, y);
      ctx.fillStyle = "#fff";
      ctx[operation]();
      ctx.beginPath();
      ctx.arc(
        x,
        y + slidingData.l / 2,
        slidingData.r,
        1.5 * slidingData.PI,
        0.5 * slidingData.PI
      );
      // 合成 xor属性作用:重叠部分变透明
      ctx.globalCompositeOperation = "xor";
      ctx.fill();
    }

    // 初始化图像
    const InitImg = () => {
      ctx.canvasCtx.drawImage(thisData.img, 0, 0, slidingData.w, slidingData.h);
      ctx.blockBoxCtx.drawImage(
        thisData.img,
        0,
        0,
        slidingData.w,
        slidingData.h
      );
      const y = thisData.y - slidingData.r * 2; //减去突出圆的大小
      // 参数 获取那块区域坐标x,y 宽
      const imageData = ctx.blockBoxCtx.getImageData(
        thisData.x,
        y,
        data.ll,
        data.ll
      );
      thisData.blockBox.width = data.ll;
      // 后面两个参数从哪里放上去
      ctx.blockBoxCtx.putImageData(imageData, 0, y);
    };

    // 绑定事件 刷新
    let index = ref(0);
    methods.Refresh = throttle(() => {
      index.value++;
      thisData.iconRotate.style.rotate = -360 * index.value + "deg";
      Reset();
    }, 2000);
    // 关闭滑块验证
    methods.Close = () => {
      data.isOpen = false;
    };

    // ----------------按着---------------
    methods.SliderMousedownEvent = (e) => {
      eventData.originX = e.x;
      eventData.originY = e.y;
      eventData.isMouseDown = true;
    };
    // ----------------拖动---------------
    document.addEventListener("mousemove", (e) => {
      if (!eventData.isMouseDown) return false;
      const moveY = e.y - eventData.originY;
      const moveX = e.x - eventData.originX;
      // 判断时候超出或者小于0
      if (moveX < 0 || moveX + 40 >= slidingData.w) return false;
      // 拖动按钮位置
      thisData.slider.style.left = moveX + "px";
      // 拖动填充滑块位置
      const blockLeft = moveX;
      thisData.blockBox.style.left = blockLeft + "px";
      // 遮罩宽长度
      thisData.slider_mask.style.width = moveX + 40 + "px";
      // 添加位置
      eventData.trail.push(moveY);
    });
    // ----------------抬起---------------
    document.addEventListener("mouseup", () => {
      if (!eventData.isMouseDown) return false;
      else eventData.isMouseDown = false;
      // 验证位置;
      const spliced = Verify();
      if (spliced) {
        // 添加成功
        data.slideVerifyStatus = 2;
        SuccessCallback();
        methods.Close();
      } else {
        // 添加失败样式
        data.slideVerifyStatus = 4;
        FailCallback();
        setTimeout(() => {
          Reset();
        }, 1000);
      }
    });

    // 清除
    function CleanCtx() {
      ctx.canvasCtx.clearRect(0, 0, slidingData.w, slidingData.h);
      ctx.blockBoxCtx.clearRect(0, 0, slidingData.w, slidingData.h);
      thisData.blockBox.width = slidingData.w;
    }

    // 重置
    async function Reset() {
      data.slideVerifyStatus = 0;
      thisData.slider.style.left = 0;
      thisData.blockBox.style.left = 0;
      thisData.slider_mask.style.width = 0;
      await CleanCtx();
      await GetData();
    }

    // 验证
    function Verify() {
      const left = parseInt(thisData.blockBox.style.left);
      return Math.abs(left - thisData.x) < 1; //10表示容错率,值越小,需要拼得越精确
    }

    //#endregion

    //#region 子父传递事件
    async function EventGlobal() {
      data.currentGlobalData.$bus.on("openSlideVerify", (boolValue) => {
        if (data.isOpen) return;
        data.isOpen = boolValue;
        Reset();
      });
    }

    // 成功总事件
    function SuccessCallback() {
      context.emit("successCallback");
    }
    // 失败总事件
    function FailCallback() {
      context.emit("failCallback");
    }

    //#endregion

    return {
      ...toRefs(data),
      ...methods,
    };
  },
};
</script>

  完整代码

<template>
  <!--滑块验证的包裹-->
  <div class="slide-authCode-wrap" v-show="isOpen">
    <!--箭头-->
    <div class="arrow"></div>
    <!--关闭-->
    <div class="close" @click="Close">
      <span class="iconfont icon-chacha"></span>
    </div>

    <!--滑块主要容器-->
    <div class="validate-wrap">
      <!--header 头部-->
      <div class="refresh">
        <div class="refresh-text">完成拼图验证</div>
        <!--刷新区域-->
        <div class="refresh-icon" @click="Refresh">
          <!--刷新按钮Icon-->
          <span class="icon iconfont icon-gengxin" ref="iconRotate"></span>
          <span>换一张</span>
        </div>
      </div>
      <!--滑块区域-->
      <div class="slider-main-container">
        <!-- 画布容器Box -->
        <div id="captcha" ref="captcha" style="position: relative">
          <!-- 画布bg -->
          <canvas ref="canvas_bg" width="364" height="142"
            >浏览器版本过低,请升级浏览器
          </canvas>
          <!-- 滑块box -->
          <canvas ref="blockBox" width="364" height="142" class="block"
            >浏览器版本过低,请升级浏览器
          </canvas>
          <!--显示的图片-->
          <img ref="img" src="" style="display: none" width="0" height="0" />
          <!-- <img
            ref="img"
            src="./images/722-300x150.jpg"
            width="0"
            height="0"
            style="display: none"
          /> -->
          <!-- 拖动容器Box -->
          <div
            class="slider-container"
            :class="slideVerifyStatus === 4 ? 'slider-container-fail' : ''"
          >
            <div class="slide-bg">
              <div class="left"></div>
              <div class="center">拖动滑块完成拼图,进行账号验证</div>
              <div class="right"></div>
            </div>
            <!-- 拖动遮罩 -->
            <div ref="slider_mask" class="slider-mask">
              <!-- 拖动块 -->
              <div
                ref="slider"
                class="slider"
                @mousedown="SliderMousedownEvent"
              >
                <!-- 拖动Icon -->
                <span
                  class="slider-icon iconfont"
                  :class="[
                    slideVerifyStatus === 0 && 'icon-tubiao-xiaoshou',
                    slideVerifyStatus === 2 && 'icon-gouxuan',
                    slideVerifyStatus === 4 && 'icon-close',
                  ]"
                >
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import {
  computed,
  getCurrentInstance,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  toRefs,
} from "vue";
//第三方模块
import throttle from "lodash/throttle"; // 引入节流会防抖插件
import { ElMessage } from "element-plus";
//自定义模块
import { mapActionsFun } from "@/hooks/VueX";
//获取两个值之间的随机数
import { GetRandomNumberByRange } from "@/utils/Random";
// //回去两个值之间的随机数
// function GetRandomNumberByRange(start, end) {
//     return Math.round(Math.random() * (end - start) + start);
// }
import GetGlobalData from "@/utils/Global/GetGlobalData";

export default {
  name: "SliderVerification",
  emits: ["successCallback", "failCallback"],
  setup(props, context) {
    const data = reactive({
      ll: 0,
      slideVerifyStatus: 0, //控制滑块的状态Icon
      isOpen: false,
    });

    const slidingData = {
      l: 35, //去掉突出的部分滑块总边长
      r: 7.5, //滑块突出的小圆圈半径
      w: 364, //canvas宽度
      h: 142, //canvas高度
      PI: Math.PI, //2PI = 360
      ll: 0, //滑块的实际边长(包括突出部分)
    };
    data.ll = computed(() => {
      return slidingData.l + slidingData.r * 2; //滑块的实际边长(包括突出部分)
    });
    const thisData = {
      x: 0,
      y: 0,
      captcha: null,
      canvas_bg: null,
      blockBox: null,
      img: null,
      slider_mask: null,
      slider: null,
      url: "",
      iconRotate: null,
      currentGlobalData: null,
    };
    const ctx = {
      canvasCtx: null,
      blockBoxCtx: null,
    };
    const methods = {
      Refresh: null,
      close: null,
      SliderMousedownEvent: null,
    };
    const eventData = {
      originX: 0,
      originY: 0,
      trail: [],
      isMouseDown: false,
    };

    //#region 生命周期
    const currentInstance = getCurrentInstance();
    data.currentGlobalData = GetGlobalData();
    onMounted(async () => {
      await GetElement();
      await EventGlobal();
    });

    onBeforeUnmount(() => {
      data.currentGlobalData.$bus.all.delete("openSlideVerify");
    });

    //#endregion

    //#region 封装方法
    const GetData = throttle(async () => {
      try {
        // Ajax请求 获取图片
        thisData.url = (
          await mapActionsFun(["getVerifySlideImg"]).getVerifySlideImg()
        ).url;
        thisData.img.src = thisData.url;
        await DrawInitialize();
        await InitImg();
      } catch (e) {
        ElMessage({
          showClose: true,
          dangerouslyUseHTMLString: true,
          type: "error",
          message: e,
          duration: 1500,
        });
      }
    }, 1500);

    // 获取需要的元素
    const GetElement = async () => {
      thisData.captcha = currentInstance.proxy.$refs.captcha;
      thisData.canvas_bg = currentInstance.proxy.$refs.canvas_bg;
      thisData.blockBox = currentInstance.proxy.$refs.blockBox;
      thisData.img = currentInstance.proxy.$refs.img;
      thisData.slider_mask = currentInstance.proxy.$refs.slider_mask;
      thisData.slider = currentInstance.proxy.$refs.slider;
      thisData.iconRotate = currentInstance.proxy.$refs.iconRotate;
      // alpha(boolean):表示canvas是否包含一个alpha通道,设为false则浏览器知道背景永远不透明,能加速对于透明场景和图像的绘制。
      // willReadFrequently(Boolean):表示是否计划有大量的回读操作,频繁调用getImageData()方法时能节省内存,仅Gecko内核浏览器支持。
      // storage(String):声明使用的storage类型,默认为”persistent”。
      ctx.canvasCtx = thisData.canvas_bg.getContext("2d", {
        willReadFrequently: true,
      });
      ctx.blockBoxCtx = thisData.blockBox.getContext("2d", {
        willReadFrequently: true,
      });
    };
    // 绘画方法
    const DrawInitialize = () => {
      thisData.x = GetRandomNumberByRange(
        data.ll + 10,
        slidingData.w - (data.ll + 10)
      );
      thisData.y = GetRandomNumberByRange(
        slidingData.r * 2 + 10,
        slidingData.h - (data.ll + 10)
      );
      // fill 通过填充路径的内容区域生成实心的图形
      // clip 把已经创建的路径转换成裁剪路径。裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。
      // 注意:clip()只能遮罩在这个方法调用之后绘制的图像,如果是clip()方法调用之前绘制的图像,则无法实现遮罩。
      Draw(ctx.canvasCtx, "fill", thisData.x, thisData.y);
      Draw(ctx.blockBoxCtx, "clip", thisData.x, thisData.y);
    };

    // 绘制  2D渲染,渲染方式,坐标(X,Y)
    function Draw(ctx, operation, x, y) {
      // ★前提要创建Canvas 否者无法绘画
      // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径
      ctx.beginPath();
      // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。
      ctx.moveTo(x, y);
      //(绘制一条从当前位置到指定坐标x,y)的直线. 假如 x 60~304 y 25~82
      ctx.lineTo(x + slidingData.l / 2, y);
      // 绘制圆弧 x, y, r, startAngle, endAngle, anticlockwise 六个参数
      // 以(x, y)为圆心,以r为半径,从 startAngle弧度开始到endAngle弧度结束。anticlosewise是布尔值,true表示逆时针,false表示顺时针。(默认是顺时针)
      // 注意1 这里的度数都是弧度 👉 0弧度是指的x轴正方形
      // 注意2 arc绘制的坐标是从最开始的位置计算的
      ctx.arc(
        x + slidingData.l / 2,
        y - slidingData.r,
        slidingData.r,
        0,
        2 * slidingData.PI
      );
      // 回到原来起步画圆的位置
      ctx.lineTo(x + slidingData.l / 2, y);
      // 半径 直径是指 L
      // 向右再走 直径 位置
      ctx.lineTo(x + slidingData.l, y);
      // 向右再走 半径 位置
      ctx.lineTo(x + slidingData.l, y + slidingData.l / 2);
      // arc方法 须从最开始的位置计算 走到最右边 再向右走半径位置 向下走半径位置
      ctx.arc(
        x + slidingData.l + slidingData.r,
        y + slidingData.l / 2,
        slidingData.r,
        0,
        2 * slidingData.PI
      );
      ctx.lineTo(x + slidingData.l, y + slidingData.l / 2);
      ctx.lineTo(x + slidingData.l, y + slidingData.l);
      ctx.lineTo(x, y + slidingData.l);
      ctx.lineTo(x, y);
      ctx.fillStyle = "#fff";
      ctx[operation]();
      ctx.beginPath();
      ctx.arc(
        x,
        y + slidingData.l / 2,
        slidingData.r,
        1.5 * slidingData.PI,
        0.5 * slidingData.PI
      );
      // 合成 xor属性作用:重叠部分变透明
      ctx.globalCompositeOperation = "xor";
      ctx.fill();
    }

    // 初始化图像
    const InitImg = () => {
      ctx.canvasCtx.drawImage(thisData.img, 0, 0, slidingData.w, slidingData.h);
      ctx.blockBoxCtx.drawImage(
        thisData.img,
        0,
        0,
        slidingData.w,
        slidingData.h
      );
      const y = thisData.y - slidingData.r * 2; //减去突出圆的大小
      // 参数 获取那块区域坐标x,y 宽
      const imageData = ctx.blockBoxCtx.getImageData(
        thisData.x,
        y,
        data.ll,
        data.ll
      );
      thisData.blockBox.width = data.ll;
      // 后面两个参数从哪里放上去
      ctx.blockBoxCtx.putImageData(imageData, 0, y);
    };

    // 绑定事件 刷新
    let index = ref(0);
    methods.Refresh = throttle(() => {
      index.value++;
      thisData.iconRotate.style.rotate = -360 * index.value + "deg";
      Reset();
    }, 2000);
    // 关闭滑块验证
    methods.Close = () => {
      data.isOpen = false;
    };

    // ----------------按着---------------
    methods.SliderMousedownEvent = (e) => {
      eventData.originX = e.x;
      eventData.originY = e.y;
      eventData.isMouseDown = true;
    };
    // ----------------拖动---------------
    document.addEventListener("mousemove", (e) => {
      if (!eventData.isMouseDown) return false;
      const moveY = e.y - eventData.originY;
      const moveX = e.x - eventData.originX;
      // 判断时候超出或者小于0
      if (moveX < 0 || moveX + 40 >= slidingData.w) return false;
      // 拖动按钮位置
      thisData.slider.style.left = moveX + "px";
      // 拖动填充滑块位置
      const blockLeft = moveX;
      thisData.blockBox.style.left = blockLeft + "px";
      // 遮罩宽长度
      thisData.slider_mask.style.width = moveX + 40 + "px";
      // 添加位置
      eventData.trail.push(moveY);
    });
    // ----------------抬起---------------
    document.addEventListener("mouseup", () => {
      if (!eventData.isMouseDown) return false;
      else eventData.isMouseDown = false;
      // 验证位置;
      const spliced = Verify();
      if (spliced) {
        // 添加成功
        data.slideVerifyStatus = 2;
        SuccessCallback();
        methods.Close();
      } else {
        // 添加失败样式
        data.slideVerifyStatus = 4;
        FailCallback();
        setTimeout(() => {
          Reset();
        }, 1000);
      }
    });

    // 清除
    function CleanCtx() {
      ctx.canvasCtx.clearRect(0, 0, slidingData.w, slidingData.h);
      ctx.blockBoxCtx.clearRect(0, 0, slidingData.w, slidingData.h);
      thisData.blockBox.width = slidingData.w;
    }

    // 重置
    async function Reset() {
      data.slideVerifyStatus = 0;
      thisData.slider.style.left = 0;
      thisData.blockBox.style.left = 0;
      thisData.slider_mask.style.width = 0;
      await CleanCtx();
      await GetData();
    }

    // 验证
    function Verify() {
      const left = parseInt(thisData.blockBox.style.left);
      return Math.abs(left - thisData.x) < 1; //10表示容错率,值越小,需要拼得越精确
    }

    //#endregion

    //#region 子父传递事件
    async function EventGlobal() {
      data.currentGlobalData.$bus.on("openSlideVerify", (boolValue) => {
        if (data.isOpen) return;
        data.isOpen = boolValue;
        Reset();
      });
    }

    // 成功总事件
    function SuccessCallback() {
      context.emit("successCallback");
    }
    // 失败总事件
    function FailCallback() {
      context.emit("failCallback");
    }

    //#endregion

    return {
      ...toRefs(data),
      ...methods,
    };
  },
};
</script>

<style scoped lang="less">
//滑块验证
.slide-authCode-wrap {
  position: absolute;
  left: 0;
  z-index: 110;
  bottom: 65px;
  width: 364px;
  height: 216.5px;
  padding: 12px 12px 12px 20px;
  border: 1px solid #eee;
  box-shadow: 0 0 2px 2px #eee;
  background-color: #fff;

  //关闭验证
  .close {
    cursor: pointer;
    z-index: 100;
    position: absolute;
    right: 10px;
    top: 10px;
    display: block;
    width: 20px;
    height: 20px;
    line-height: 20px;

    span {
      font-size: 20px;

      &:active {
        color: #a4a4a4;
      }
    }
  }

  //箭头
  .arrow {
    display: block;
    position: absolute;
    background-image: url("./images/tips.gif");
    background-repeat: no-repeat;
    width: 16px;
    height: 8px;
    background-position: 0 -8px;
    overflow: hidden;
    bottom: -8px;
    left: 190px;
  }

  //滑块主要容器
  .validate-wrap {
    //提醒区域
    .refresh {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-right: 30px;
      font-family: Helvetica, Tahoma, Arial, "Microsoft YaHei", "微软雅黑",
        sans-serif;

      .refresh-text {
        font-size: 15px;
        color: #666;
      }

      .refresh-icon {
        cursor: pointer;
        color: #06c;

        .icon {
          display: inline-block;
          margin-right: 4px;
          vertical-align: revert;
          transition: 0.6s linear;
        }

        span {
          font-size: 15px;
        }
      }
    }

    //滑块组要容器
    .slider-main-container {
      margin-top: 5px;
      //画布容器Box
      #captcha {
        display: flex;
        justify-content: center;
        flex-direction: column;
        /* 小拼图 */

        .block {
          position: absolute;
          left: 0;
          top: 0;
        }

        /* 滑动条 */

        .slider-container {
          position: relative;
          margin: 10px auto 0;
          opacity: 1;
          font-size: 14px;
          visibility: visible;
          width: 364px;
          height: 40px;
          line-height: 40px;
          text-align: center;
          color: #05a4ea;

          //滑块Bg
          .slide-bg {
            .left {
              float: left;
              width: 40px;
              height: 40px;
              background: url("./images/slide-left-icon2.png") no-repeat;
            }

            .center {
              background-image: url("./images/slide-center-bg.png");
              margin-left: 40px;
              margin-right: 40px;
              overflow: hidden;
              white-space: nowrap;
              user-select: none;
              -moz-user-select: none;
              -ms-user-select: none;
              -webkit-user-select: none;
            }

            .right {
              width: 40px;
              height: 40px;
              background: url("./images/slide-right-icon2.png") no-repeat;
              position: absolute;
              right: 0;
              top: 0;
            }
          }

          /* 拖动遮罩容器 */

          .slider-mask {
            position: absolute;
            top: 0;
            left: 0;
            width: 364px;
            height: 40px;
            border-radius: 36px;
            border: 0px solid #1991fa;
            background: linear-gradient(#33b5fb, #8fdfff);
          }

          /* 拖动块 */

          .slider {
            position: absolute;
            left: -3px;
            top: -3px;
            width: 45px;
            height: 45px;
            background: #fff;
            box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
            cursor: pointer;
            transition: background 0.2s linear;
            border-radius: 50%;
          }

          .slider:hover {
            background: #aed6ff;
            color: #06c;
          }

          /* 拖动Icon */

          .slider-icon {
            font-size: 25px;
            font-weight: 700;
            vertical-align: middle;
          }
        }

        //活动状态CSS

        /* 滑动条失败态 */

        .slider-container-fail {
          .slider-mask {
            background: linear-gradient(#ff5e5e, #ffb3b3);
          }

          .slider {
            padding-top: 2px;
            box-sizing: border-box;
          }

          .slider-icon {
            color: red;
          }
        }
      }
    }
  }
}
</style>

  实现效果

VUE 实现滑块验证 ①

总结

以上是个人学习Vue的相关知识点,一点一滴的记录了下来,有问题请评论区指正,共同进步,这才是我写文章的原因之,如果这篇文章对您有帮助请三连支持一波文章来源地址https://www.toymoban.com/news/detail-480858.html

到了这里,关于VUE 实现滑块验证 ①的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • selenium+opencv实现模拟登陆(滑块验证码)

    selenium+opencv实现模拟登陆(滑块验证码)

    很多网站登录登陆时都要用到滑块验证码,在某些场景例如使用爬虫爬取信息时常常受到阻碍,想着用opencv的模板匹配试试能不能实现模拟登陆。本来觉得网上资料多应该还蛮容易,但实际上手还是搞了蛮久,在这里记录一下整个流程,网站无所谓主要是要有滑动验证码:

    2023年04月14日
    浏览(7)
  • uniapp-vue2-微信小程序-滑块验证组件wo-slider

    uniapp-vue2-微信小程序-滑块验证组件wo-slider

    wo-slider是一款支持高度自定义的滑块验证组件,采用uniapp-vue2编写 采用touchstart、touchmove、touchend事件实现的滑块组件,支持H5、微信小程序(其他小程序未试过,可自行尝试) 可到插件市场下载尝试: https://ext.dcloud.net.cn/search?q=wo-slider 使用示例

    2024年02月07日
    浏览(9)
  • python+selenium绕过滑块验证,实现自动登录

    python+selenium绕过滑块验证,实现自动登录

    实现taobao自动化登录,当用webdriver打开淘宝时,滑块验证一直失败,手动滑都会失败。因为淘宝会检测window.navigator.webdriver,控件检测到你是selenium进入,所以就会弹出滑块验证。只需要绕过检测就能实现自动登录 验证了两种方法可以跳过: 第一种是给浏览器加启动参数,开

    2024年02月12日
    浏览(44)
  • 爽,我终于实现了selenium图片滑块验证码!【附代码】

    爽,我终于实现了selenium图片滑块验证码!【附代码】

    因为种种原因没能实现愿景的目标,在这里记录一下中间结果,也算是一个收场吧。这篇文章主要是 用selenium解决滑块验证码的个别案列。 思路: 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方,计算要滑动的距离 规划路线,移动

    2024年02月04日
    浏览(9)
  • 用Java+Selenium+openCV实现126.com的滑块验证

    用Java+Selenium+openCV实现126.com的滑块验证

    引入OpenCV库,下载地址:Releases - OpenCV下载4.5.0即可,在下完成安装exe文件后,会出现下列文件,直接将build/java文件下的jar包导入项目的依赖即可。  1.获取驱动,加载126网址 2.切换窗口,因为登录功能是在iframe中,需要先切换窗口 3.输入账号和密码,点击登录(才能弹出滑块

    2024年01月21日
    浏览(15)
  • 爽,我终于实现了selenium图片滑块验证码【附代码】

    爽,我终于实现了selenium图片滑块验证码【附代码】

    因为种种原因没能实现愿景的目标,在这里记录一下中间结果,也算是一个收场吧。这篇文章主要是 用selenium解决滑块验证码的个别案列。 思路: 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方,计算要滑动的距离 规划路线,移动

    2024年02月04日
    浏览(8)
  • 滑块拖动验证

    滑块拖动验证

    svg图标 初始图标 成功的图标 vue组件

    2024年01月17日
    浏览(11)
  • Selenium图片滑块验证码

    Selenium图片滑块验证码

    因为种种原因没能实现愿景的目标,在这里记录一下中间结果,也算是一个收场吧。这篇文章主要是用selenium解决滑块验证码的个别案列。 思路: 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方,计算要滑动的距离 规划路线,移动

    2024年02月13日
    浏览(47)
  • 使用opencv识别滑块验证

    使用opencv识别滑块验证

    对于某些简单的滑块,无需进行模型训练,可以使用opencv就能识别,比如: 有一种常用方法cv2.matchTemplate,可以将滑动的图案与背景图案进行模板匹配,这种方法适用于滑动图案与背景图片中目标位置有相同图案的情况。 但是本文想介绍的是另一种:背景中目标位置是空白的

    2024年03月10日
    浏览(15)
  • java爬虫破解滑块验证码

    java爬虫破解滑块验证码

    使用技术:java+Selenium 废话:         有爬虫,自然就有反爬虫,就像病毒和杀毒软件一样,有攻就有防,两者彼此推进发展。而目前最流行的反爬技术验证码,为了防止爬虫自动注册,批量生成垃圾账号,几乎所有网站的注册页面都会用到验证码技术。其实验证码的英文

    2023年04月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包