先上预览图
GIF预览
思路
将整体分为两个大部分:图片部分与滑块部分
1.图片部分包括背景图和两个拼图块
2.滑块部分包括拖动区域、滑块和跟在滑块后面的进度条
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);
}
}
页面上展示
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);
});
验证成功后鼠标移动的轨迹
完整代码
<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>