在当今互联网时代,随着技术的不断进步,传统的验证码验证方式已经无法满足对安全性和用户体验的需求。为了应对日益狡猾的机器人和恶意攻击,许多网站和应用程序开始引入图形验证码,其中一种备受欢迎的形式就是图片旋转验证功能。这项技术通过利用用户交互、视觉识别和动态效果,为用户提供了一种全新、有趣且高效的验证方式。本文将深入探讨如何实现这一引人注目的图片旋转验证功能,让您轻松保护网站安全,同时提升用户体验。
效果展示
功能介绍:
在vue项目中将此验证弹框封装成一个单独的组件,完整代码如下;
登录之前点击“安全验证”进入验证模块,拖动滑轨调整图片旋转位置,完成验证功能,验证失败会自动刷新再次验证,点击“刷新”也可以收到刷新图案,这是一个由纯前端实现的验证功能;
完整代码—组件封装
<template>
<!-- 旋转验证图片 -->
<div id="rotation" v-show="isShow">
<div class="check" @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove">
<div class="title">请完成下方验证后继续操作</div>
<div class="img-con">
<!-- <img src="../assets/images/login/myn.png" :style="{ transform: imgAngle }" /> -->
<img :src="showImg" :style="{ transform: imgAngle }" />
<div v-if="showError" class="check-state">
错误
</div>
<div v-else-if="showSuccess" class="check-state">
正确
</div>
<div v-else-if="checking" class="check-state">
验证中
</div>
</div>
<div ref="sliderCon" class="slider-con" :class="{ 'err-anim': showError }">
<div ref="slider" class="slider" id="slider" :class="{ sliding }" :style="{ '--move': `${slidMove}px` }">
</div>
</div>
<div class="refresh">
<t-button variant="text" theme="default" @click="refreshImg">
<t-icon name="refresh" color="#77BB61" />刷新
</t-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'OfficeAutomationSystemClientRotationverificationImg',
data() {
return {
isShow: true, //显示
showError: false,
showSuccess: false,
checking: false,
sliding: false,
slidMove: 0,
showImg: '',
initAngle: 0,
imgList: [
{ id: 1, url: require('@/assets/images/illustration/myn.png') },
{ id: 2, url: require('@/assets/images/illustration/fn7X4S.png') },
{ id: 3, url: require('@/assets/images/illustration/head.png') },
],
};
},
computed: {
angle() {
let sliderConWidth = this.sliderConWidth ?? 0;
let sliderWidth = this.sliderWidth ?? 0;
let ratio = this.slidMove / (sliderConWidth - sliderWidth);
// console.log(ratio);
if (isNaN(ratio) || ratio == 0) {
ratio = this.initAngle;
return 360 * ratio;
} else {
return 360 * (this.initAngle + ratio);
}
},
imgAngle() {
return `rotate(${this.angle}deg)`;
}
},
mounted() {
this.refreshImg();
},
methods: {
// 刷新图片
refreshImg() {
var randElement = this.imgList[Math.floor(Math.random() * this.imgList.length)];
// console.log('img', randElement.url);
this.showImg = randElement.url;
this.resetSlider();
var randomNumber = Math.floor(Math.random() * 381) + 30;
console.log('数值',randomNumber);
this.initAngle = randomNumber / 360;
},
// 重置滑块
resetSlider() {
this.sliding = false;
this.slidMove = 0;
this.checking = false;
this.showSuccess = false;
this.showError = false;
},
onMouseDown(event) {
if (event.target.id !== "slider") {
return;
}
if (this.checking) return;
// 设置状态为滑动中
this.sliding = true;
// 下面三个变量不需要监听变化,因此不放到 data 中
this.sliderLeft = event.clientX; // 记录鼠标按下时的x位置
this.sliderConWidth = this.$refs.sliderCon.clientWidth; // 记录滑槽的宽度
this.sliderWidth = this.$refs.slider.clientWidth; // 记录滑块的宽度
},
onMouseUp(event) {
if (this.sliding && this.checking === false) {
this.checking = true;
this.validApi(this.angle)
.then((isok) => {
if (isok) {
this.showSuccess = true;
} else {
this.showError = true;
}
return new Promise((resolve, reject) => {
setTimeout(() => {
if (isok) {
resolve(Math.round(this.angle));
} else {
reject();
}
this.resetSlider();
}, 1000);
});
})
.then((angle) => {
// 处理业务,或者通知调用者验证成功
// alert("旋转正确: " + angle + "度");
// this.$message.success({
// content: '验证通过啦!',
// });
//给父组件传一个状态
this.$emit('onEmit', 'T')
})
.catch((e) => {
//alert("旋转错误");
this.$message.error({
content: '验证失败啦!',
});
});
}
},
onMouseMove(event) {
if (this.sliding && this.checking === false) {
// 滑块向右的平移距离等于鼠标移动事件的X坐标减去鼠标按下时的初始坐标。
let m = event.clientX - this.sliderLeft;
if (m < 0) {
// 如果m小于0表示用户鼠标向左移动超出了初始位置,也就是0
// 所以直接等于 0,以防止越界
m = 0;
} else if (m > this.sliderConWidth - this.sliderWidth) {
// 滑块向右移动的最大距离是滑槽的宽度减去滑块的宽度。
// 因为css的 translateX 函数是以元素的左上角坐标计算的
// 所以要减去滑块的宽度,这样滑块在最右边时,才不会左上角和滑槽右上角重合。
m = this.sliderConWidth - this.sliderWidth;
}
this.slidMove = m;
}
},
// 验证角度是否正确
validApi(angle) {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 图片已旋转的角度
const imgAngle = this.initAngle * 360;
// 图片已旋转角度和用户旋转角度之和
let sum = angle;
console.log('角度',imgAngle, angle);
// 误差范围
const errorRang = 20;
// 当用户旋转角度和已旋转角度之和为360度时,表示旋转了一整圈,也就是转正了
// 但是不能指望用户刚好转到那么多,所以需要留有一定的误差
let isOk = Math.abs(360 - sum) <= errorRang;
resolve(isOk);
}, 500);
});
}
}
};
</script>
<style lang="scss" scoped>
#rotation {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.check {
--slider-size: 42px;
width: 330px;
background: white;
// box-shadow: 0 0 5px grey;
border-radius: 16px;
padding: 20px 0;
display: flex;
flex-direction: column;
align-items: center;
.header {
width: 90%;
padding: 0 16px;
padding-bottom: 16px;
display: flex;
justify-content: space-between;
overflow: hidden;
color: #464B52;
font-size: 16px;
font-weight: 400;
}
.title {
color: #464B52;
font-size: 16px;
font-weight: 700;
margin-bottom: 16px;
}
}
.check .img-con {
position: relative;
overflow: hidden;
width: 120px;
height: 120px;
border-radius: 50%;
}
.check .img-con img {
width: 100%;
height: 100%;
user-select: none;
}
.check .slider-con {
width: 80%;
height: var(--slider-size);
border-radius: 42px;
margin-top: 1rem;
position: relative;
background: #f5f5f5;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1) inset;
}
.slider-con .slider {
background: url('../assets/images/icon/arrow.svg') no-repeat;
width: 42px;
height: 42px;
border-radius: 50%;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
cursor: move;
--move: 0px;
transform: translateX(var(--move));
}
.slider-con .slider.sliding {
background: url('../assets/images/icon/arrow.svg') no-repeat;
}
.check-state {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
color: white;
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
body {
padding: 0;
margin: 0;
background: #fef5e0;
}
.refresh {
margin-top: 15px;
height: 22px;
vertical-align: middle;
cursor: none;
.t-button .t-button__text,
.t-button .t-icon {
margin-right: 5px;
padding-top: 2px;
}
}
@keyframes jitter {
20% {
transform: translateX(-5px);
}
40% {
transform: translateX(10px);
}
60% {
transform: translateX(-5px);
}
80% {
transform: translateX(10px);
}
100% {
transform: translateX(0);
}
}
.slider-con.err-anim {
animation: jitter 0.5s;
}
.slider-con.err-anim .slider {
background: #ff4e4e;
}
</style>