VUE3--实现图片滑动验证功能(vite2+vue3.2)

vue

滑动验证是多种登录验证方式中相对操作比较便捷的一种方式,本文基于vite2+vue3.2环境实现了滑动验证码,使用了script setup这种语法糖方式开发组件功能,这样使得Composition API使用更加便捷高效,得心应手。

概要

滑动验证是多种登录验证方式中相对操作比较便捷的一种方式,本文基于vite2+vue3.2环境实现了滑动验证码,使用了script setup这种语法糖方式开发组件功能,这样使得Composition API使用更加便捷高效,得心应手。

初始化项目

使用npm执行以下命令:

npm init vite@latest

按照操作提示进行操作即可

配置vite.config.js

import { defineConfig } from \'vite\'

import vue from \'@vitejs/plugin-vue\'

import path from \'path\'

// https://vitejs.dev/config/

export default defineConfig({

plugins: [vue()],

resolve: {

alias: {

\'@\': path.resolve(__dirname, \'./src\'),

\'public\': path.resolve(__dirname, \'./public\')

}

}

})

assets目录下新建images目录,导入600X400的背景图和图标雪碧图

滑动验证组件代码

template部分代码

<template>

<div

class="sv-box"

:style="{width: w + \'px\'}"

onselectstart="return false">

<!-- 加载背景图片遮罩层 -->

<div :class="{\'bg-mask\': state.loadFlag}"></div>

<div

v-show="show"

class="sv-refresh"

@click="refresh">

</div>

<canvas

:width="w"

:height="h"

ref="canvas">

</canvas>

<canvas

:width="w"

:height="h"

ref="block"

class="canvas-block">

</canvas>

<!-- 滑动条容器 -->

<div

class="slider"

:class="{

\'active\': state.sliderActive,

\'success\': state.sliderSuccess,

\'fail\': state.sliderFail

}">

<div

class="slider--mask"

:style="{width: state.sliderMaskWidth}">

<!-- 滑动块 -->

<div

class="slider--inner"

:style="{left: state.sliderLeft}"

@mousedown="sliderDown"

@touchstart="touchStartEvt"

@touchmove="touchMoveEvt"

@touchend="touchEndEvt">

<div class="slider--icon"></div>

</div>

</div>

<span class="slider--text">{{ text }}</span>

</div>

</div>

</template>

script部分代码

<script setup>

import bg1 from \'@/assets/images/bg1.png\'

import bg2 from \'@/assets/images/bg2.png\'

import bg3 from \'@/assets/images/bg3.png\'

import bg4 from \'@/assets/images/bg4.png\'

import bg5 from \'@/assets/images/bg5.png\'

import { ref, onMounted, reactive } from \'vue\'

const props = defineProps({

l: {

type: Number,

default: 42

},

r: {

type: Number,

default: 10

},

w: {

type: Number,

default: 310

},

h: {

type: Number,

default: 155

},

text: {

type: String,

default: \'向右侧滑动\'

},

accuracy: {

type: Number,

default: 5

},

show: {

type: Boolean,

default: true

},

bgList: {

type: Array,

default: () => [bg1, bg2, bg3, bg4, bg5]

}

})

const PI = Math.PI

const sum = (x, y) => {

return x + y

}

const square = (x) => {

return x * x

}

const state = reactive({

sliderActive: false,

sliderSuccess: false,

sliderFail: false,

canvasCtx: null,

blockCtx: null,

block: null,

blockX: undefined,

blockY: undefined,

L: props.l + props.r * 2 + 3,

img: undefined,

originX: undefined,

originY: undefined,

isMouseDown: false,

trail: [],

sliderLeft: \'0px\',

sliderMaskWidth: 0,

success: false,

loadFlag: false,

timestamp: null

})

const block = ref(null)

const canvas = ref(null)

const emit = defineEmits([\'refresh\', \'success\', \'fail\', \'again\', \'fulfilled\'])

const init = () => {

initDom()

initImg()

bindEvt()

}

const initDom = () => {

state.block = block.value

state.canvasCtx = canvas.value.getContext(\'2d\')

state.blockCtx = state.block.getContext(\'2d\')

}

const initImg = () => {

const img = createImg(() => {

state.loadFlag = false

drawBlock()

state.canvasCtx.drawImage(img, 0, 0, props.w, props.h)

state.blockCtx.drawImage(img, 0, 0, props.w, props.h)

const _y = state.blockY - props.r * 2 -1

const imageDate = state.blockCtx.getImageData(state.blockX, _y, state.L, state.L)

state.block.width = state.L

state.blockCtx.putImageData(imageDate, 0, _y)

})

state.img = img

}

const bindEvt = () => {

const moveEvt = (e) => {

if (!state.isMouseDown) return

const moveX = e.clientX - state.originX

const moveY = e.clientY - state.originY

if (moveX < 0 || moveX + 40 >= props.w) return

state.sliderLeft = moveX + \'px\'

const blockLeft = (props.w - 40 - 20) / (props.w - 40) * moveX

state.block.style.left = blockLeft + \'px\'

state.sliderActive = true // add active

state.sliderMaskWidth = moveX + \'px\'

state.trail.push(moveY)

}

const upEvt = (e) => {

if (!state.isMouseDown) return

state.isMouseDown = false

if (e.clientX === state.originX) return

state.sliderActive = false // remove active

state.timestamp = new Date() - state.timestamp

const {

isPass,

isRobot

} = verify()

if (isPass) {

if(props.accuracy === -1) {

state.sliderSuccess = true

state.success = true

emit(\'success\', state.timestamp)

return

}

if (isRobot) {

// succ

state.sliderSuccess = true

state.success = true

emit(\'success\', state.timestamp)

} else {

state.sliderFail = true

emit(\'again\')

}

} else {

state.sliderFail = true

emit(\'fail\')

setTimeout(() => {

reset()

}, 1000)

}

}

document.addEventListener(\'mousemove\', moveEvt)

document.addEventListener(\'mouseup\', upEvt)

}

const sliderDown = (e) => {

if (state.success) return

state.originX = e.clientX

state.originY = e.clientY

state.isMouseDown = true

state.timestamp = new Date()

}

const touchStartEvt = (e) => {

if (state.success) return

state.originX = e.changedTouches[0].pageX

state.originY = e.changedTouches[0].pageY

state.isMouseDown = true

state.timestamp = new Date()

}

const touchMoveEvt = (e) => {

if (!state.isMouseDown) return

const moveX = e.changedTouches[0].pageX - state.originX

const moveY = e.changedTouches[0].pageY - state.originY

if (moveX < 0 || moveX + 38 >= state.w) return

state.sliderLeft = moveX + \'px\'

let blockLeft = (props.w - 40 - 20) / (props.w - 40) * moveX

state.block.style.left = blockLeft + \'px\'

state.sliderActive = true

state.sliderMaskWidth = moveX + \'px\'

state.trail.push(moveY)

}

const touchEndEvt = (e) => {

if (!state.isMouseDown) return

state.isMouseDown = false

if (e.changedTouches[0].pageX === this.originX) return

state.sliderActive = false

state.timestamp = new Date() - state.timestamp

const {

isPass,

isRobot

} = verify()

if (isPass) {

if(props.accuracy === -1) {

state.sliderSuccess = true

state.success = true

emit(\'success\', state.timestamp)

return

}

if (isRobot) {

// succ

state.sliderSuccess = true

state.success = true

emit(\'success\', state.timestamp)

} else {

state.sliderFail = true

emit(\'again\')

}

} else {

state.sliderFail = true

emit(\'fail\')

setTimeout(() => {

reset()

}, 1000)

}

}

const reset = () => {

state.success = false

state.sliderFail = false

state.sliderSuccess = false

state.sliderFail = false

state.sliderLeft = 0

state.block.style.left = 0

state.sliderMaskWidth = 0

// canvas

const {

w,

h

} = props

state.canvasCtx.clearRect(0, 0, w, h)

state.blockCtx.clearRect(0, 0, w, h)

state.block.width = w

// generate img

state.img.src = getRandomBg()

emit(\'fulfilled\')

}

const refresh = () => {

reset()

emit(\'refresh\')

}

const drawBlock = () => {

state.blockX = getRandomNumberByRange(state.L + 10, props.w - (state.L + 10))

state.blockY = getRandomNumberByRange(props.r + 10, props.h - (state.L + 10))

draw(state.canvasCtx, state.blockX, state.blockY, \'fill\')

draw(state.blockCtx, state.blockX, state.blockY, \'clip\')

}

const draw = (ctx, x, y, type) => {

const { l, r } = props

ctx.beginPath()

ctx.moveTo(x, y)

ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)

ctx.lineTo(x + l, y)

ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)

ctx.lineTo(x + l, y + l)

ctx.lineTo(x, y + l)

ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)

ctx.lineTo(x, y)

ctx.lineWidth = 2

ctx.fillStyle = \'rgba(255, 255, 255, 0.7)\'

ctx.strokeStyle = \'rgba(255, 255, 255, 0.7)\'

ctx.stroke()

ctx[type]()

ctx.globalCompositeOperation = \'destination-over\'

}

const createImg = (onload) => {

const img = document.createElement(\'img\')

img.crossOrigin = \'Anonymous\'

img.onload = onload

img.error = () => {

console.error(\'Background image failed to load\')

}

img.src = getRandomBg()

return img

}

const getRandomBg = () => {

const len = props.bgList.length

return props.bgList[getRandomNumberByRange(0, len)]

}

const getRandomNumberByRange = (start, end) => {

return Math.round(Math.random() * (end - start) + start)

}

const verify = () => {

const arr = state.trail // 拖拽时y轴的轨迹坐标

const average = arr.reduce(sum) / arr.length // 纵坐标的平均值

const deviations = arr.map(y => y - average) // 纵坐标与其平均值的插值数组

const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) // 标准差

const left = parseInt(state.block.style.left)

const accuracy = props.accuracy <= 1 ? 1 : props.accuracy > 10 ? 10 : props.accuracy

return {

isPass: Math.abs(left - state.blockX) <= accuracy,

isRobot: average !== stddev

}

}

onMounted(() => {

init()

})

</script>

style部分代码

<style lang="scss" scoped>

.sv-box {

position: relative;

.bg-mask {

position: absolute;

top: 0;

right: 0;

bottom: 0;

left: 0;

background: rgba(255, 255, 255, 0.9);

z-index: 999;

animation: loading 1.5s infinite;

}

@keyframes loading {

0% {

opacity: 0.7;

}

100% {

opacity: 1;

}

}

.canvas-block {

position: absolute;

left: 0;

top: 0;

}

.sv-refresh {

position: absolute;

right: 0;

top: 0;

width: 34px;

height: 34px;

cursor: pointer;

background: url("@/assets/images/icons-sprite.png");

background-position: 34px 35px;

z-index: 999;

}

.slider {

position: absolute;

text-align: center;

width: 100%;

height: 40px;

line-height: 40px;

margin-top: 15px;

color: #45494c;

border: 1px solid #e4e7eb;

background: #f7f9fa;

.slider--mask {

position: absolute;

left: -1px;

top: -1px;

height: 40px;

border: 1px solid transparent;

background: transparent;

.slider--inner {

position: absolute;

top: 0px;

left: 0px;

width: 40px;

height: 40px;

background: #fff;

cursor: pointer;

transition: background .2s linear;

.slider--icon {

position: absolute;

top: 15px;

left: 13px;

width: 14px;

height: 12px;

background: url("@/assets/images/icons-sprite.png");

background-position: 34px 445px;

}

&:hover {

background: #1991fa;

.slider--icon {

background-position: 34px 458px;

}

}

}

}

}

.active .slider--mask {

border: 1px solid #1991fa;

}

.active .slider--inner {

top: -1px !important;

border: 1px solid #1991fa;

background-color: #1991fa !important;

}

.success .slider--mask {

border: 1px solid #52ccba;

}

.success .slider--inner {

top: -1px !important;

border: 1px solid #52ccba;

background-color: #52ccba !important;

}

.success .slider--icon {

background-position: 0 0 !important;

}

.fail .slider--mask {

border: 1px solid #f57a7a;

background-color: #fce1e1;

}

.fail .slider--inner {

top: -1px !important;

border: 1px solid #f57a7a;

background-color: #f57a7a !important;

}

.active .slider--text,

.success .slider--text,

.fail .slider--text {

display: none;

}

}

</style>

效果图

以上是 VUE3--实现图片滑动验证功能(vite2+vue3.2) 的全部内容, 来源链接: utcz.com/z/378849.html

回到顶部