vant+vue3+ts 的滑块验证
效果图
<template>
<div ref="stage" class="stage">
<div id="slider" ref="slider" class="slider">
<div class="label">向右滑动验证</div>
<div id="track" ref="track" class="track">
<div id="bg-green" class="bg-green"></div>
</div>
<div
id="btn"
ref="btn"
class="button"
@touchend="touchend"
@touchmove="touchmove"
@touchstart="touchstart"
>
<div id="icon" class="icon">
<van-icon v-if="data.isSuccess === state[2]" color="#07c160" name="passed" />
<van-icon v-else-if="data.isSuccess === state[0]" name="arrow" />
<van-icon v-else name="close" color="#ee0a24" />
</div>
<div id="spinner" class="spinner"></div>
</div>
</div>
</div>
</template>
type dataInterface = {
oW: number
oLeft: number
isMove: boolean
isSuccess: string
}
const state: Array<string> = ['wait', 'error', 'success']
const data = reactive<dataInterface>({
isMove: true,
oW: 0,
oLeft: 0,
isSuccess: state[0],
})
const btn = ref<any>({})
const track = ref<any>({})
const slider = ref<any>({})
const stage = ref<any>({})
const touchstart = (e: TouchEvent) => {
if (!data.isMove) return
data.isSuccess = state[0]
const touches = e.changedTouches[0]
data.oW = touches.clientX - btn.value?.offsetLeft
btn.value.className = 'button'
track.value.className = 'track'
}
const touchmove = (e: TouchEvent) => {
const touches = e.touches[0]
if (data.isMove) {
data.oLeft = touches.clientX - data.oW
if (data.oLeft < 0) {
data.oLeft = 0
} else if (data.oLeft >= slider.value.offsetWidth - btn.value.offsetWidth) {
data.oLeft = slider.value.offsetWidth - btn.value.offsetWidth
}
btn.value.style.transform = `translateX(${data.oLeft}px)`
track.value.style.width = `${data.oLeft}px`
}
}
const isError = () => {
stage.value.classList.add('login-error')
stage.value.addEventListener(
'animationend',
() => {
if (data.isSuccess === state[1] || data.isSuccess === state[0]) {
btn.value.style.transform = `translateX(${0}px)`
track.value.style.width = 0
if (stage.value.classList.contains('login-error')) {
stage.value.classList.remove('login-error')
data.isSuccess = state[0]
}
}
},
false,
)
}
const touchend = () => {
if (data.oLeft >= slider.value.clientWidth - btn.value.offsetWidth) {
btn.value.style.transform = `translateX(${data.oLeft}px)`
data.isMove = false
track.value.style.width = `${data.oLeft}px`
data.isSuccess = state[2]
} else {
data.isSuccess = state[1]
isError()
}
btn.value.className = 'button-on'
track.value.className = 'track-on'
}
const validate = (): boolean => {
if (data.isSuccess === state[0] || data.isSuccess === state[1]) {
isError()
return false
}
return true
}
// 暴露一个获取验证的方法
defineExpose({ validate })
@keyframes slidetounlock {
0% {
background-position: -200px 0;
}
100% {
background-position: 200px 0;
}
}
@-webkit-keyframes slidetounlock {
0% {
background-position: -200px 0;
}
100% {
background-position: 200px 0;
}
}
@-webkit-keyframes bouncedelay {
0%,
80%,
100% {
-webkit-transform: scale(0);
}
40% {
-webkit-transform: scale(1);
}
}
@keyframes bouncedelay {
0%,
80%,
100% {
transform: scale(0);
-webkit-transform: scale(0);
}
40% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
@keyframes errtips {
10% {
transform: translateX(3px);
}
20% {
transform: translateX(-3px);
}
30% {
transform: translateX(3px);
}
40% {
transform: translateX(-3px);
}
50% {
transform: translateX(3px);
}
60% {
transform: translateX(-3px);
}
70% {
transform: translateX(3px);
}
80% {
transform: translateX(-3px);
}
90% {
transform: translateX(3px);
}
100% {
transform: translateX(0);
}
}
.login-error {
border: 1px solid red !important;
animation: errtips 0.5s;
.text {
color: red;
}
}
.stage {
position: relative;
height: 44px;
border: 1px solid #ccc;
width: 100%;
.slider {
position: absolute;
height: 100%;
width: 100%;
}
.label {
background-image: -webkit-linear-gradient(
left,
#e9a5a5,
#b8c1c0 10%,
#65c0e0 20%,
#aea2db 30%,
#81c1d9 40%,
#e9a5a5 50%,
#b8c1c0 60%,
#65c0e0 70%,
#aea2db 80%,
#81c1d9 90%,
#e9a5a5
);
font-weight: 800;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-animation: slidetounlock 3s infinite;
-webkit-text-size-adjust: none;
line-height: 44px;
height: 100%;
text-align: center;
font-size: 14px;
width: 100%;
color: #aaa;
}
.button {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 100%;
background: #fff;
transition: all 0s;
color: #ccc;
-webkit-transition: all 0s;
}
.button-on {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 100%;
background-color: #fff;
transition: all 1s;
-webkit-transition: all 0.5s;
}
.track {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0;
overflow: hidden;
transition: width 0s;
-webkit-transition: width 0s;
}
.track-on {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0;
overflow: hidden;
transition: width 1s;
-webkit-transition: width 0.5s;
}
.icon {
height: 100%;
width: 100%;
color: #ccc;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
}
.bg-green {
line-height: 34px;
height: 100%;
text-align: center;
font-size: 14px;
background-color: var(--van-button-primary-background-color);
color: #fff;
}
}
引用
<van-field>
<template #input><slidingVerification ref="validate" /></template>
</van-field>
<script lang="ts" setup>
import { reactive } from 'vue'
import slidingVerification from '@/components/slidingVerification.vue'
const validate = ref()
// 获取是否验证通过
validate.value.validate()
</script>