从0-1开发一个Vue3前端系统页面-6.滑动验证码

不再会有谎言 2024-09-19 08:33:01 阅读 55

本节实现登录页面的滑动验证码功能

 滑动验证码新增优化在6.1,链接如下:从0-1开发一个Vue3前端系统页面-6.1滑动验证码优化-CSDN博客

icon-default.png?t=N7T8

https://blog.csdn.net/XiaomeiGuiSnJs/article/details/141159971?spm=1001.2014.3001.5501

1.安装插件

<code>npm install --save vue-monoplasty-slide-verify

2.从Git上获取源码

源码地址:github

icon-default.png?t=N7T8

https://so.csdn.net/so/search?q=github&spm=1001.2101.3001.7020文档地址: GitHub - monoplasty/vue-monoplasty-slide-verify: vue slide verify online previewvue slide verify online preview. Contribute to monoplasty/vue-monoplasty-slide-verify development by creating an account on GitHub.

icon-default.png?t=N7T8

https://github.com/monoplasty/vue-monoplasty-slide-verify

3.创建工具类

在本地项目中src下新建一个工具类文件夹utils用作存储工具类并创建一个包用于存储滑动验证码组件,项目结构参考如图

4.打开Git源码

1.将lib下的文件复制到新建的滑动验证码包(slideVerify)中

2.将assets下的icon_light.png复制到本地项目中

此处在assets下新建了一个静态工具类utils用于存储项目工具类中需要的静态资源,与项目研发所需静态资源进行了区分。

5.修改源码

因为本地项目与源码项目使用的静态文件路径不同,所以在源码slide-Verify.vue中Ctrl+F寻找assets然后对图片icon_light.png的相对路径进行修改。参考如图:

源码:

修改后:

源码中共两处需要修改,分别是如图的P387和P438行。

6.配置main.js

7.本地项目中使用

<code><template>

<!-- 滑动验证插件 SLIDEVERIFY 的使用 -->

<div class="divout">code>

<slide-verify :l="42" :r="10" :w="310" :h="155" ref="slideblock" @again="onAgain" @fulfilled="onFulfilled"code>

@success="onSuccess" @fail="onFail" @refresh="onRefresh" :slider-text="text" :accuracy="accuracy"code>

:imgs="imgs"></slide-verify>code>

</div>

</template>

<script>

// prop参数说明

// 参数 类型 备注

// l Number 滑块边长

// r Number 滑块突出圆形半径

// w Number canvas width

// h Number canvas height

// sliderText String 滑块底部文字

// imgs Array picture array 背景图数组,默认值 []

// accuracy Number 滑动验证的误差范围,默认值 5

// show Boolean 是否显示刷新按钮,默认值 true

// 回调函数

// 回调函数 备注

// success success callback(返回时间参数,单位为毫秒)

// fail fail callback

// refresh 点击刷新按钮后的回调函数

// again 检测到非人为操作滑动时触发的回调函数

// fulfilled 刷新成功之后的回调函数

export default {

data() {

return {

text: "向右滑动->", // 设置滑块文字

// 精确度小,可允许的误差范围小;为1时,则表示滑块要与凹槽完全重叠,才能验证成功。默认值为5

accuracy: 2,

imgs: [

"https://img1.baidu.com/it/u=1890390320,3399874998&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800",

"https://img1.baidu.com/it/u=1546227440,2897989905&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",

"https://lmg.jj20.com/up/allimg/1113/052420110515/200524110515-1-1200.jpg",

"https://lmg.jj20.com/up/allimg/1114/0G020114924/200G0114924-11-1200.jpg",

],

};

},

methods: {

// 验证通过

onSuccess(times) {

console.log("验证通过,耗时 " + times + "毫秒");

},

// 验证失败

onFail() {

console.log("验证不通过");

},

// 滑块上的刷新

onRefresh() {

console.log("点击了刷新小图标");

},

// 刷新后执行的回调函数

onFulfilled() {

console.log("刷新成功啦!");

},

// 检测是否人为操作

onAgain() {

console.log("检测到非人为操作的哦!");

this.msg = "try again";

// 刷新

this.$refs.slideblock.reset();

},

// 重置刷新

handleClick() {

this.$refs.slideblock.reset();

},

},

};

</script>

<style scoped></style>

8.回到页面查看 

 滑动验证码组件配置成功

 附件

考虑到有些同学可能进不去GitHub,所以这里把源码奉上。

注意:这里的源码若与我的路径不同的话会报路径错误,需要各位自行修改相对路径。

 index.js

<code>import SlideVerify from "./slide-Verify.vue";

const plugins = {

install(Vue) {

Vue.component(SlideVerify.name, SlideVerify);

},

};

if (typeof window !== "undefined" && window.Vue) {

window.Vue.use(SlideVerify);

}

export default plugins;

slide-Verify.vue

<template>

<div class="slide-verify" :style="{ width: w + 'px' }" id="slideVerify" onselectstart="return false;">code>

<!-- 图片加载遮蔽罩 -->

<div :class="{ 'slider-verify-loading': loadBlock }"></div>code>

<canvas :width="w" :height="h" ref="canvas"></canvas>code>

<div v-if="show" @click="refresh" class="slide-verify-refresh-icon"></div>code>

<canvas :width="w" :height="h" ref="block" class="slide-verify-block"></canvas>code>

<!-- container -->

<div class="slide-verify-slider" :class="{

'container-active': containerActive,

'container-success': containerSuccess,

'container-fail': containerFail

}">code>

<div class="slide-verify-slider-mask" :style="{ width: sliderMaskWidth }">code>

<!-- slider -->

<div @mousedown="sliderDown" @touchstart="touchStartEvent" @touchmove="handleMoveEvent($event, 'touch')"code>

@touchend="handleMoveEndEvent($event, 'touch')" class="slide-verify-slider-mask-item"code>

:style="{ left: sliderLeft }">code>

<div class="slide-verify-slider-mask-item-icon"></div>code>

</div>

</div>

<span class="slide-verify-slider-text">{ { sliderText }}</span>code>

</div>

</div>

</template>

<script>

const PI = Math.PI;

function sum(x, y) {

return x + y;

}

function square(x) {

return x * x;

}

export default {

name: "SlideVerify",

props: {

// block length

l: {

type: Number,

default: 42

},

// block radius

r: {

type: Number,

default: 10

},

// canvas width

w: {

type: Number,

default: 310

},

// canvas height

h: {

type: Number,

default: 155

},

sliderText: {

type: String,

default: "Slide filled right"

},

accuracy: {

type: Number,

default: 5 // 若为 -1 则不进行机器判断

},

show: {

type: Boolean,

default: true

},

imgs: {

type: Array,

default: () => []

}

},

data() {

return {

containerActive: false, // container active class

containerSuccess: false, // container success class

containerFail: false, // container fail class

canvasCtx: null,

blockCtx: null,

block: null,

block_x: undefined, // container random position

block_y: undefined,

L: this.l + this.r * 2 + 3, // block real length

img: undefined,

originX: undefined,

originY: undefined,

isMouseDown: false,

trail: [],

sliderLeft: 0, // block right offset

sliderMaskWidth: 0, // mask width,

success: false, // Bug Fixes 修复了验证成功后还能滑动

loadBlock: true, // Features 图片加载提示,防止图片没加载完就开始验证

timestamp: null

};

},

mounted() {

this.init();

},

methods: {

init() {

this.initDom();

this.initImg();

this.bindEvents();

},

initDom() {

this.block = this.$refs.block;

this.canvasCtx = this.$refs.canvas.getContext("2d");

this.blockCtx = this.block.getContext("2d");

},

initImg() {

const img = this.createImg(() => {

// 图片加载完关闭遮蔽罩

this.loadBlock = false;

this.drawBlock();

this.canvasCtx.drawImage(img, 0, 0, this.w, this.h);

this.blockCtx.drawImage(img, 0, 0, this.w, this.h);

let { block_x: x, block_y: y, r, L } = this;

let _y = y - r * 2 - 1;

let ImageData = this.blockCtx.getImageData(x, _y, L, L);

this.block.width = L;

this.blockCtx.putImageData(ImageData, 0, _y);

});

this.img = img;

},

drawBlock() {

this.block_x = this.getRandomNumberByRange(

this.L + 10,

this.w - (this.L + 10)

);

this.block_y = this.getRandomNumberByRange(

10 + this.r * 2,

this.h - (this.L + 10)

);

this.draw(this.canvasCtx, this.block_x, this.block_y, "fill");

this.draw(this.blockCtx, this.block_x, this.block_y, "clip");

},

draw(ctx, x, y, operation) {

let { l, r } = this;

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[operation]();

// Bug Fixes 修复了火狐和ie显示问题

ctx.globalCompositeOperation = "destination-over";

},

createImg(onload) {

const img = document.createElement("img");

img.crossOrigin = "Anonymous";

img.onload = onload;

img.onerror = () => {

img.src = this.getRandomImg();

};

img.src = this.getRandomImg();

return img;

},

// 随机生成img src

getRandomImg() {

// return require('../assets/img.jpg')

const len = this.imgs.length;

return len > 0

? this.imgs[this.getRandomNumberByRange(0, len - 1)]

: // "https://bing.ioliu.cn/v1/rand?w=300&h=150";

"https://source.unsplash.com/300x150/?book,library";

// "https://api.dujin.org/pic/fengjing";

},

getRandomNumberByRange(start, end) {

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

},

refresh() {

this.reset();

this.$emit("refresh");

},

sliderDown(event) {

if (this.success) return;

this.originX = event.clientX;

this.originY = event.clientY;

this.isMouseDown = true;

this.timestamp = +new Date();

},

touchStartEvent(e) {

if (this.success) return;

this.originX = e.changedTouches[0].pageX;

this.originY = e.changedTouches[0].pageY;

this.isMouseDown = true;

this.timestamp = +new Date();

},

bindEvents() {

document.addEventListener("mousemove", this.handleMoveEvent);

document.addEventListener("mouseup", this.handleMoveEndEvent);

},

// 处理函数抽离

handleMoveEvent: throttle(function (e, type = "mouse") {

if (!this.isMouseDown) return false;

const moveX =

type === "mouse"

? e.clientX - this.originX

: e.changedTouches[0].pageX - this.originX;

const moveY =

type === "mouse"

? e.clientY - this.originY

: e.changedTouches[0].pageY - this.originY;

if (moveX < 0 || moveX + 38 >= this.w) return false;

this.sliderLeft = moveX + "px";

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

this.block.style.left = blockLeft + "px";

this.containerActive = true; // add active

this.sliderMaskWidth = moveX + "px";

this.trail.push(moveY);

}),

handleMoveEndEvent(e, type = "mouse") {

if (!this.isMouseDown) return false;

this.isMouseDown = false;

if (

(type === "mouse" && e.clientX === this.originX) ||

(type === "touch" && e.changedTouches[0].pageX === this.originX)

)

return false;

this.containerActive = false; // remove active

this.timestamp = +new Date() - this.timestamp;

const { spliced, TuringTest } = this.verify();

if (spliced) {

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

this.containerSuccess = true;

this.success = true;

this.$emit("success", this.timestamp);

return;

}

if (TuringTest) {

// succ

this.containerSuccess = true;

this.success = true;

this.$emit("success", this.timestamp);

} else {

this.containerFail = true;

this.$emit("again");

}

} else {

this.containerFail = true;

this.$emit("fail");

setTimeout(() => {

this.reset();

}, 1000);

}

},

verify() {

const arr = this.trail; // drag y move distance

const average = arr.reduce(sum) / arr.length; // average

const deviations = arr.map(x => x - average); // deviation array

const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length); // standard deviation

const left = parseInt(this.block.style.left);

const accuracy =

this.accuracy <= 1 ? 1 : this.accuracy > 10 ? 10 : this.accuracy;

return {

spliced: Math.abs(left - this.block_x) <= accuracy,

TuringTest: average !== stddev // equal => not person operate

};

},

reset() {

this.success = false;

this.containerActive = false;

this.containerSuccess = false;

this.containerFail = false;

this.sliderLeft = 0;

this.block.style.left = 0;

this.sliderMaskWidth = 0;

// canvas

let { w, h } = this;

this.canvasCtx.clearRect(0, 0, w, h);

this.blockCtx.clearRect(0, 0, w, h);

this.block.width = w;

// generate img

this.img.src = this.getRandomImg();

this.$emit("fulfilled");

}

},

destroyed() {

document.removeEventListener("mousemove", this.handleMoveEvent);

document.removeEventListener("mouseup", this.handleMoveEndEvent);

}

};

function throttle(

fn,

interval = 50,

options = { leading: true, trailing: true }

) {

const { leading, trailing, resultCallback } = options;

let lastTime = 0;

let timer = null;

const _throttle = function (...args) {

return new Promise((resolve, reject) => {

const nowTime = new Date().getTime();

if (!lastTime && !leading) lastTime = nowTime;

const remainTime = interval - (nowTime - lastTime);

if (remainTime <= 0) {

if (timer) {

clearTimeout(timer);

timer = null;

}

const result = fn.apply(this, args);

if (resultCallback) resultCallback(result);

resolve(result);

lastTime = nowTime;

return;

}

if (trailing && !timer) {

timer = setTimeout(() => {

timer = null;

lastTime = !leading ? 0 : new Date().getTime();

const result = fn.apply(this, args);

if (resultCallback) resultCallback(result);

resolve(result);

}, remainTime);

}

});

};

_throttle.cancel = function () {

if (timer) clearTimeout(timer);

timer = null;

lastTime = 0;

};

return _throttle;

}

</script>

<style scoped>

.slide-verify {

position: relative;

}

/* 图片加载样式 */

.slider-verify-loading {

position: absolute;

top: 0;

right: 0;

left: 0;

bottom: 0;

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

z-index: 999;

animation: loading 1.5s infinite;

}

@keyframes loading {

0% {

opacity: 0.7;

}

100% {

opacity: 9;

}

}

.slide-verify-block {

position: absolute;

left: 0;

top: 0;

}

.slide-verify-refresh-icon {

position: absolute;

right: 0;

top: 0;

width: 34px;

height: 34px;

cursor: pointer;

background: url("../../../assets/utils/icon_light.png") 0 -437px;

background-size: 34px 471px;

}

.slide-verify-slider {

position: relative;

text-align: center;

width: 100%;

height: 40px;

line-height: 40px;

margin-top: 15px;

background: #f7f9fa;

color: #45494c;

border: 1px solid #e4e7eb;

}

.slide-verify-slider-mask {

position: absolute;

left: 0;

top: 0;

height: 40px;

border: 0 solid #1991fa;

background: #d1e9fe;

}

.slide-verify-slider-mask-item {

position: absolute;

top: 0;

left: 0;

width: 40px;

height: 40px;

background: #fff;

box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);

cursor: pointer;

transition: background 0.2s linear;

}

.slide-verify-slider-mask-item:hover {

background: #1991fa;

}

.slide-verify-slider-mask-item:hover .slide-verify-slider-mask-item-icon {

background-position: 0 -13px;

}

.slide-verify-slider-mask-item-icon {

position: absolute;

top: 15px;

left: 13px;

width: 14px;

height: 12px;

background: url("../../../assets/utils/icon_light.png") 0 -26px;

background-size: 34px 471px;

}

.container-active .slide-verify-slider-mask-item {

height: 38px;

top: -1px;

border: 1px solid #1991fa;

}

.container-active .slide-verify-slider-mask {

height: 38px;

border-width: 1px;

}

.container-success .slide-verify-slider-mask-item {

height: 38px;

top: -1px;

border: 1px solid #52ccba;

background-color: #52ccba !important;

}

.container-success .slide-verify-slider-mask {

height: 38px;

border: 1px solid #52ccba;

background-color: #d2f4ef;

}

.container-success .slide-verify-slider-mask-item-icon {

background-position: 0 0 !important;

}

.container-fail .slide-verify-slider-mask-item {

height: 38px;

top: -1px;

border: 1px solid #f57a7a;

background-color: #f57a7a !important;

}

.container-fail .slide-verify-slider-mask {

height: 38px;

border: 1px solid #f57a7a;

background-color: #fce1e1;

}

.container-fail .slide-verify-slider-mask-item-icon {

top: 14px;

background-position: 0 -82px !important;

}

.container-active .slide-verify-slider-text,

.container-success .slide-verify-slider-text,

.container-fail .slide-verify-slider-text {

display: none;

}

</style>

 icon_light.png



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。