vue3滑动拼图验证码简单实现

448 阅读5分钟

先上预览图

image.png

GIF预览

_001.gif

思路

将整体分为两个大部分:图片部分与滑块部分

1.图片部分包括背景图和两个拼图块

2.滑块部分包括拖动区域、滑块和跟在滑块后面的进度条

image.png

HTML代码

<template>
  <h1 style="text-align: center; font-size: 30px">行为验证码</h1>
  <div class="box">
    <!-- 验证成功后展示的遮罩层 使用antd里的result组件 -->
    <a-result status="success" v-if="success" class="success" />
    <div class="check" :style="{ 'background-image': 'url(' + imgurl + ')' }">
      <!-- 遮罩块 -->
      <div class="check-content" ref="content"></div>
      <!-- 拖动块 -->
      <div class="check-block" ref="check"></div>
    </div>
    <div class="drag" ref="drag">
      <!-- 跟在滑块后的进度条 -->
      <div class="progress" ref="progress"></div>
      <!-- 滑块 -->
      <div class="drag-block" ref="dragBlock"></div>
      <!-- 文字提示 -->
      <div class="drag-tips">按住按钮完成验证</div>
    </div>
  </div>
</template>

CSS代码

.box {
  position: relative;
  width: 500px;
  margin: auto;
  .success {
    margin: auto;
    width: 500px;
    height: 360px;
    line-height: 320px;
    position: absolute;
    background-color: #000;
    opacity: 0.5;
    z-index: 100;
  }
}
.check {
  width: 500px;
  height: 300px;
  background-size: 100% 100%;
  .check-content {
    position: absolute;
    top: 50px;
    left: 250px;
    width: 50px;
    height: 50px;
    background-color: #000;
    opacity: 0.8;
    //五边形
    clip-path: polygon(50% 0%, 91% 23%, 80% 76%, 50% 100%, 20% 76%, 9% 23%);
  }
  .check-block {
    width: 50px;
    height: 50px;
    background-image: inherit;
    background-repeat: inherit;
    background-size: 500px 300px;
    background-position: -250px -50px;
    position: absolute;
    top: 50px;
    clip-path: polygon(50% 0%, 91% 23%, 80% 76%, 50% 100%, 20% 76%, 9% 23%);
    left: 0px;
  }
}
.drag {
  width: 500px;
  height: 50px;
  background-color: #e3e3e3;
  margin-top: 10px;
  position: relative;
  border-radius: 10px;
  .drag-block {
    width: 50px;
    height: 50px;
    background-color: rgb(124, 124, 124);
    z-index: 1;
    position: absolute;
    top: 0;
    left: 0;
    border-radius: 10px;
  }
  .drag-tips {
    z-index: 0;
    width: 95%;
    height: 100%;
    margin: 0 auto;
    text-align: center;
    line-height: 50px;
    font-size: 15px;
    user-select: none;
  }
  .progress {
    border-radius: 10px;
    position: absolute;
    width: 0px;
    height: 100%;
    background-color: rgb(97, 97, 253);
  }
}

页面上展示

image.png

JS代码

// 可以后端提供url
const imgurl = "";
// 验证成功后遮罩层显示
const success = ref(false);
// 获取元素实例
const drag = ref(null);
const dragBlock = ref(null);
const content = ref(null);
const check = ref(null);
const progress = ref(null);
// 随机生成要拼接的区域content的位置
const { x, y } = { x: 150 + Math.random() * 300, y: Math.random() * 250 };
// 运动状态 代表鼠标是否被按下
let Motion = false;
//存储鼠标按下的x坐标
let startX = 0;
//存储移动的位置
let offsetX = 0;
// 鼠标拖动时的移动轨迹
let track = [];

X和Y值也可以通过后端获取,我这边是直接随机生成

挂载后对元素实例添加鼠标动作监听器

onMounted(() => {
  content.value.style = `left:${x}px;top:${y}px`;
  check.value.style = `background-position:-${x}px -${y}px; top:${y}px`;
  drag.value.addEventListener("mousemove", (e) => {
    if (!Motion) return;
    // 计算移动的位置
    offsetX = e.pageX - startX;
    // 鼠标轨迹存入
    track.push({ x: e.pageX, y: e.pageY });
    //判断移动距离是否正确
    if (offsetX < 0 || offsetX > 450) return;
    //修改可移动盒子的 x坐标
    dragBlock.value.style.transform = `translateX(${offsetX}px)`;
    // 设置被验证滑块left值
    check.value.style.left = offsetX + "px";
    // 设置进度条长度
    progress.value.style.width = offsetX + 25 + "px";
  });
  // 左键点击
  dragBlock.value.addEventListener("mousedown", (e) => {
    Motion = true;
    startX = e.pageX;
  });
  // 左键抬起
  const mouseup = (e) => {
    Motion = false;
    // 左右2px范围
    if (offsetX >= x - 2 && offsetX <= x + 2) {
      // 验证成功 蒙层出现
      message.success("验证成功");
      success.value = true;
      // 删除左键抬起的监听器
      document.removeEventListener("mouseup", mouseup);
      // 鼠标移动的轨迹,可以上传给后端进行分析
      console.log(track);
    } else {
      // 验证失败 滑块和被验证块恢复坐标
      dragBlock.value.style.transform = "translateX(0px)";
      check.value.style.left = "0px";
      progress.value.style.width = "0px";
      track = [];
    }
  };
  document.addEventListener("mouseup", mouseup);
});

验证成功后鼠标移动的轨迹

image.png 也可以添加其他的逻辑来校验,比如操作时间等

完整代码

<template>
  <h1 style="text-align: center; font-size: 30px">行为验证码</h1>
  <div class="box">
    <a-result status="success" v-if="success" class="success" />
    <div class="check" :style="{ 'background-image': 'url(' + imgurl + ')' }">
      <!-- 遮罩块 -->
      <div class="check-content" ref="content"></div>
      <!-- 拖动块 -->
      <div class="check-block" ref="check"></div>
    </div>

    <div class="drag" ref="drag">
      <!-- 跟在滑块后的进度条 -->
      <div class="progress" ref="progress"></div>
      <!-- 滑块 -->
      <div class="drag-block" ref="dragBlock"></div>
      <!-- 文字提示 -->
      <div class="drag-tips">按住按钮完成验证</div>
    </div>
  </div>
</template>

<script setup>
import { message } from "ant-design-vue";
import { onMounted, ref, toRef } from "vue";
// 可以后端提供url
const imgurl = "";
const success = ref(false);
// 获取元素实例
const drag = ref(null);
const dragBlock = ref(null);
const content = ref(null);
const check = ref(null);
const progress = ref(null);
// 随机生成要拼接的区域content的位置
const { x, y } = { x: 150 + Math.random() * 300, y: Math.random() * 250 };
// 运动状态 代表鼠标是否被按下
let Motion = false;
//存储鼠标按下的x坐标
let startX = 0;
//存储移动的位置
let offsetX = 0;
// 鼠标拖动时的移动轨迹
let track = [];
onMounted(() => {
  content.value.style = `left:${x}px;top:${y}px`;
  check.value.style = `background-position:-${x}px -${y}px; top:${y}px`;
  drag.value.addEventListener("mousemove", (e) => {
    if (!Motion) return;
    // 计算移动的位置
    offsetX = e.pageX - startX;
    // 鼠标轨迹存入
    track.push({ x: e.pageX, y: e.pageY });
    //判断移动距离是否正确
    if (offsetX < 0 || offsetX > 450) return;
    //修改可移动盒子的 x坐标
    dragBlock.value.style.transform = `translateX(${offsetX}px)`;
    // 设置被验证滑块left值
    check.value.style.left = offsetX + "px";
    // 设置进度条长度
    progress.value.style.width = offsetX + 25 + "px";
  });
  dragBlock.value.addEventListener("mousedown", (e) => {
    Motion = true;
    startX = e.pageX;
  });
  const mouseup = (e) => {
    Motion = false;
    if (offsetX >= x - 2 && offsetX <= x + 2) {
      // 验证成功 蒙层出现
      message.success("验证成功");
      success.value = true;
      // 删除左键抬起的监听器
      document.removeEventListener("mouseup", mouseup);
      console.log(track);
    } else {
      // 验证失败 滑块和被验证块恢复坐标
      dragBlock.value.style.transform = "translateX(0px)";
      check.value.style.left = "0px";
      progress.value.style.width = "0px";
      track = [];
    }
  };
  document.addEventListener("mouseup", mouseup);
});
</script>

<style scoped lang="less">
.box {
  position: relative;
  width: 500px;
  margin: auto;
  .success {
    margin: auto;
    width: 500px;
    height: 360px;
    line-height: 320px;
    position: absolute;
    background-color: #000;
    opacity: 0.5;
    z-index: 100;
  }
}
.check {
  width: 500px;
  height: 300px;
  background-size: 100% 100%;
  .check-content {
    position: absolute;
    top: 50px;
    left: 250px;
    width: 50px;
    height: 50px;
    background-color: #000;
    opacity: 0.8;
    clip-path: polygon(50% 0%, 91% 23%, 80% 76%, 50% 100%, 20% 76%, 9% 23%);
  }
  .check-block {
    width: 50px;
    height: 50px;
    background-image: inherit;
    background-repeat: inherit;
    background-size: 500px 300px;
    background-position: -250px -50px;
    position: absolute;
    top: 50px;
    clip-path: polygon(50% 0%, 91% 23%, 80% 76%, 50% 100%, 20% 76%, 9% 23%);
    left: 0px;
  }
}

.drag {
  width: 500px;
  height: 50px;
  background-color: #e3e3e3;
  margin-top: 10px;
  position: relative;
  border-radius: 10px;
  .drag-block {
    width: 50px;
    height: 50px;
    background-color: rgb(124, 124, 124);
    z-index: 1;
    position: absolute;
    top: 0;
    left: 0;
    border-radius: 10px;
  }
  .drag-tips {
    z-index: 0;
    width: 95%;
    height: 100%;
    margin: 0 auto;
    text-align: center;
    line-height: 50px;
    font-size: 15px;
    user-select: none;
  }
  .progress {
    border-radius: 10px;
    position: absolute;
    width: 0px;
    height: 100%;
    background-color: rgb(97, 97, 253);
  }
}
</style>