DIY可视化实现UniApp手机滑块验证组件,支持自定义背景图片、成功提示、错误提示、划动提示等。
手机滑块验证组件是一种广泛应用于手机应用、网页等场景的用户验证机制,其主要目的是通过用户的滑动操作来验证用户的真实性和操作意图,从而增强系统的安全性。以下是对手机滑块验证组件的详细介绍:
一、工作原理
滑块验证组件的工作原理基于人机交互的思想,通过要求用户在屏幕上滑动指定的滑块到特定位置来完成验证。这一过程结合了用户的视觉和运动能力,使得验证过程更加难以被自动化程序绕过。
二、组成元素
手机滑块验证组件通常包含以下几个关键元素:
- 滑块:用户需要操作的对象,通常是一个可拖动的图标或图形。
- 背景图:滑块需要与之匹配或对齐的图像,背景图上通常包含有特定的图案或元素,用于引导用户找到正确的滑动位置。
- 指示信息:提示用户如何操作的文字或图形信息,如“拖动滑块完成验证”。
- 验证逻辑:后台的验证算法,用于判断用户的滑动操作是否符合预期,包括滑块的位置、滑动轨迹等。
三、操作流程
- 显示验证页面:当用户需要进行验证时,系统会生成一张包含滑块和背景图的验证码图片,并显示在用户的手机上。
- 用户操作:用户用手指按住滑块,并沿着屏幕指示的方向拖动,直到滑块到达背景图的指定位置。
- 验证结果:系统会根据用户的滑动操作进行验证,如果验证通过,用户将获得验证成功的反馈;如果验证失败,用户需要重新进行验证。
四、优势与应用
手机滑块验证组件具有以下优势:
- 提高安全性:通过结合用户的视觉和运动能力进行验证,增加了自动化程序绕过的难度。
- 提升用户体验:相比传统的验证码输入方式,滑块验证更加简单直观,用户只需进行简单的滑动操作即可完成验证。
- 适用范围广:可以灵活应用于各种手机应用、网页等场景,满足不同的验证需求。
手机滑块验证组件广泛应用于各类网站和移动应用中,如用户注册、登录、重置密码等场景。它通过简单的滑动操作,有效地保护了用户的账户安全,并提升了用户体验。
五、技术实现
手机滑块验证组件的实现通常涉及图像处理、机器学习等技术。系统需要能够生成复杂的背景图、随机放置滑块位置、捕捉用户的滑动轨迹等。同时,还需要具备高效的验证算法,以快速准确地判断用户的滑动操作是否符合预期。
综上所述,手机滑块验证组件是一种简单有效且广泛应用的用户验证机制,它通过结合用户的视觉和运动能力进行验证,提高了系统的安全性和用户体验。
六、UniApp组件库实现
<template>
<view class="diy-verify" v-if="initShow" :class="isModal?'diy-verify-modal':''" :id="elid" @touchmove.stop.prevent="stopMoveHandle" :style="verifyWidthStyle">
<view class="diy-verify-close" @tap="close" v-if="isModal">
<u-icon :size="50" color="#fff" name="close"></u-icon>
</view>
<view class="diy-verify-wrap" v-if="isShow">
<view class="diy-verify-box">
<image class="diy-verify-img" v-if="verifyImg" :src="verifyImg" mode="scaleToFill"></image>
<!-- 右侧用来验证的滑块 -->
<view class="diy-verify-block-verify" :style="blockVerifyStyle"></view>
<!-- 被css操控的滑块 -->
<view class="diy-verify-block-move" :style="blockMoveStyle"></view>
<view class="diy-verify-tips" v-if="!showBottomVerify">
<text class="diy-verify-tips-text" :style="blockTipsStyle">{{ tips }}</text>
</view>
<!-- 手指触摸的滑块 -->
<view class="diy-verify-block-touch" :style="blockTouchStyle" @touchstart="touchstartHandle"
@touchmove="touchmoveHandle" @touchend="touchendHandle">
</view>
<view @tap="initVerify()" class="diy-verify-refresh">
<u-icon :size="50" :color="refreshColor" name="reload"></u-icon>
</view>
</view>
<view class="diy-verify-verify" v-if="showBottomVerify" :style="verifyBottomStyle">
<view :class="(isSuccess?'diy-verify-success-text':'diy-verify-text')">{{tips}}</view>
<!-- 被css操控的滑块 -->
<view class="diy-verify-verify-move" :style="verifyMoveStyle">
<u-icon :size="bottomSize-10" :name="isSuccess?'checkbox-mark':'arrow-right-double'"></u-icon>
</view>
<!-- 手指触摸的滑块 -->
<view class="diy-verify-verify-touch" :style="verifyTouchStyle" @touchstart="touchstartHandle" @touchmove="touchmoveHandle" @touchend="touchendHandle">
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'zmmVerifyVerify',
emits: ["update:modelValue",'close',"change"],
props: {
// 通过双向绑定控制组件的弹出与收起
modelValue: {
type: Boolean,
default: false
},
//是否弹窗验证
isModal:{
type:Boolean,
default: false
},
//提醒
tip: {
type: String,
default: '请将左侧透明滑块拖进白色框内'
},
successTip: {
type: String,
default: "验证通过"
},
failTip:{
type: String,
default: "验证失败"
},
//滑块大小
verifySize: {
type: Number,
default: 50
},
//滑块颜色
verifyColor: {
type: String,
default: 'rgba(0,0,0,0.4)'
},
//图片验证高度
verifyHeight:{
type: Number,
default: 170
},
// 图片
verifyImg: {
type: String,
default: ''
},
//刷新颜色
refreshColor: {
type: String,
default: '#ffffff'
},
//校验正负差值区间像素
between: {
type: Number,
default: 10
},
//如果不显示底部滑动条时提示字段颜色
tipColor:{
type: String,
default: '#ffff00'
},
// 是否显示底部滑动条
showBottomVerify: {
type: Boolean,
default: false
},
//底部滑块大小
bottomSize: {
type: Number,
default: 40
},
bottomBarColor: {
type: String,
default: '#eee'
},
bottomSuccessColor: {
type: String,
default: '#19be6b'
},
//底部滑块颜色
bottomBgColor: {
type: String,
default: '#ffffff'
},
bottomColor: {
type: String,
default: '#bbbbbb'
},
bottomBorderColor: {
type: String,
default: '#bbbbbb'
},
},
data() {
return {
elid: this.$u.guid(),
verifyWidth:280,
initShow:!this.isModal,
tips:this.tip,
startPageX: 0, //开始距离
moveLeft: 0, //滑动距离
isSuccess: false, //是否成功
autoLeft: 80, //验证滑块随机的像素
autoTop: 80, //验证滑块随机的top像素
isShow: false
};
},
watch: {
verifyImg: {
immediate: true,
handler(){
this.init()
}
}
},
computed: {
verifyWidthStyle(){
return `--diy-verify-width:${this.verifyWidth}px;--diy-verify-height:${this.verifyHeight}px`
},
blockVerifyStyle() {
return `top:${this.autoTop}px;left:${this.autoLeft}px;height:${this.verifySize}px;width:${this.verifySize}px;background-color:${this.verifyColor};`
},
blockMoveStyle() {
let moveLeft = this.isSuccess?this.autoLeft:this.moveLeft;
return `top:${this.autoTop}px;left:${moveLeft}px;height:${this.verifySize}px;width:${this.verifySize}px;background-color: ${this.verifyColor};`
},
blockTipsStyle(){
return `color:${this.tipColor}`
},
blockTouchStyle() {
return `top:${this.autoTop}px;height:${this.verifySize}px;width:${this.verifySize}px;`
},
verifyMoveStyle() {
let moveLeft = this.isSuccess?this.autoLeft:this.moveLeft;
return `border:1px solid ${this.bottomBorderColor};left:${moveLeft}px;height:${this.bottomSize}px;width:${this.bottomSize}px;color:${this.bottomColor};background-color: ${this.bottomBgColor};`
},
verifyTouchStyle() {
return `height:${this.bottomSize}px;width:${this.bottomSize}px;`
},
verifyBottomStyle() {
return `font-size:${this.bottomSize-26}px;line-height:${this.bottomSize}px;height:${this.bottomSize}px;background-color:${this.isSuccess?this.bottomSuccessColor:this.bottomBarColor}`
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
if(this.initShow){
this.$nextTick(()=>{
this.$uGetRect('#' + this.elid).then((res) => {
this.verifyWidth = this.isModal?res.width-20:res.width;
this.initVerify()
});
})
}
},
initVerify(){
this.isShow = true
this.moveLeft = 0;
this.isSuccess = false;
this.autoTop = this.randPostion(0, this.verifyHeight - this.verifySize);
this.autoLeft = this.randPostion(this.verifySize + 20, this.verifyWidth - this.verifySize);
},
show(){
this.initShow = true;
this.init();
},
close(){
if(this.isModal){
this.initShow = false;
this.$emit("close");
}
},
// 拦截其他触摸事件防止nvue下input等元素层级问题
stopMoveHandle(e) {
if (e.preventDefault) {
// 阻止页面滚动
e.preventDefault()
}
},
// 随机数
randPostion(min, max) {
//返回包括最大/小值
return Math.floor(Math.random() * (max - min + 1)) + min;
},
//按下
touchstartHandle(e) {
if (this.isSuccess) {
return;
}
this.startPageX = e.changedTouches[0].pageX;
},
// 滑动
touchmoveHandle(e) {
// 滑动分两个块来操作不然会有数据抖动
if (this.isSuccess) {
return;
}
var left = e.changedTouches[0].pageX - this.startPageX; //补偿起始位置
this.moveLeft = left;
},
// 滑动离开(最终)
touchendHandle(e) {
var endLeft = e.changedTouches[0].pageX;
var verifyLeft = this.autoLeft + this.startPageX; //补偿起始位置
var chazhi = verifyLeft - endLeft; //最终差值
// 判断是否在正负差值区间
if (chazhi >= 0 - this.between && chazhi <= this.between) {
this.isSuccess = true;
// 通过会执行成功和关闭
this.tips = this.successTip
this.$emit("update:modelValue", true);
this.$emit("change", true);
uni.showToast({
icon:'none',
title:this.successTip?this.successTip:'验证通过'
})
this.close()
} else {
this.tips = this.failTip?this.failTip:'验证失败';
setTimeout(()=>{
this.tips = this.tip;
})
// 失败会执行失败并重新初始化
this.show();
uni.showToast({
title: this.failTip?this.failTip:'验证失败',
icon: 'none'
});
this.$emit("update:modelValue", false);
this.$emit("change", false);
}
}
}
};
</script>
<style lang="scss" scoped>
.diy-verify {
--diy-verify-width:280px;
--diy-verify-height:170px;
--textColor:#4d4d4d;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
&.diy-verify-modal{
position: fixed;
top:0px;
left:0px;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 99999999;
transition: all 0.3s ease-in-out 0s;
.diy-verify-close{
position: absolute;
top:10px;
right: 10px;
}
.diy-verify-wrap {
opacity: 1;
transition-duration: 0.3s;
-ms-transform: scale(1);
transform: scale(1);
overflow-x: hidden;
overflow-y: auto;
pointer-events: auto;
}
}
.diy-verify-wrap {
display: flex;
flex-direction: column;
position: relative;
margin: 0 auto;
width: var(--diy-verify-width);
background-color: #ffffff;
.diy-verify-tips {
position: absolute;
left:0;
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, .6);
font-size: 12px;
line-height: 20px;
text-align: center;
.diy-verify-tips-text {
color: #ff0;
}
}
.diy-verify-refresh{
position: absolute;
right: 10px;
top: 10px;
}
.diy-verify-text{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
color: #FFFFFF;
text-align: center;
background: -webkit-gradient(linear,left top,right top,color-stop(0,var(--textColor)),color-stop(.4,var(--textColor)),color-stop(.5,#fff),color-stop(.6,var(--textColor)),color-stop(1,var(--textColor)));
animation: slidetounlock 3s infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.diy-verify-success-text{
position: absolute;
left: 0;
top: 0;
width: 100%;
text-align: center;
color: #FFFFFF;
}
@keyframes slidetounlock{
0% {
background-position: -200rpx 0;
}
100% {
background-position: 200rpx 0;
}
}
.diy-verify-box {
position: relative;
width: var(--diy-verify-width);
height: var(--diy-verify-height);
overflow: hidden;
.diy-verify-img {
width: var(--diy-verify-width);
height: var(--diy-verify-height);
border-radius: 0px;
}
.diy-verify-block-verify,
.diy-verify-block-move,
.diy-verify-block-touch {
position: absolute;
left: 0px;
top: 0;
border-radius: 0px;
}
.diy-verify-block-verify {
border:1px solid #fff;
}
}
.diy-verify-verify {
height:40px;
width: var(--diy-verify-width);
background-color: rgba(0,0,0,0.07);
position: relative;
overflow: hidden;
.diy-verify-verify-move{
display: flex;
align-items: center;
justify-content: center;
}
.diy-verify-verify-move,
.diy-verify-verify-touch {
border-radius: 0px;
}
.diy-verify-verify-move,.diy-verify-verify-touch {
position: absolute;
left: 0px;
top: 0px;
}
}
}
}
</style>
七、组件使用
<template>
<view class="container container23285">
<view class="diygw-col-24">
<diy-verify v-model="verify" successBarColor="#ff3d0c"></diy-verify>
</view>
<view class="diygw-col-24">
<diy-verifyimg class="diygw-col-24" v-model="verifyimg" :verifySize="50" verifyImg="/static/pic1.jpg" :showBottomVerify="true"></diy-verifyimg>
</view>
<view class="clearfix"></view>
</view>
</template>
<script>
export default {
data() {
return {
//用户全局信息
userInfo: {},
//页面传参
globalOption: {},
//自定义全局变量
globalData: { logintype: '0', agree: '0' },
verify: false,
verifyimg: false
};
},
onShow() {
this.setCurrentPage(this);
},
onLoad(option) {
this.setCurrentPage(this);
if (option) {
this.setData({
globalOption: this.getOption(option)
});
}
this.init();
},
methods: {
async init() {}
}
};
</script>
<style lang="scss" scoped>
.container23285 {
}
</style>