JavaScript

超轻量级php框架startmvc

Vue实现滑动拼图验证码功能

更新时间:2020-09-12 23:24:01 作者:startmvc
缘由:之前看哔哩哔哩官网登录的时候有一个拼图验证码,很好奇怎么去实现。然后就想着

缘由:之前看哔哩哔哩官网登录的时候有一个拼图验证码,很好奇怎么去实现。然后就想着自己弄一个。先给大家看我的最终效果。后面再一点点拆解代码。

 

为什么想着写这个功能呢,主要在于拼图验证码在前端这里会比较复杂并且深入。相比文字拼写,12306的图片验证码都没有拼图验证码对前端的要求来的复杂,和难。

我总结下知识点:

1、弹窗功能

2、弹窗基于元素定位

3、元素拖动

4、canvas绘图

5、基础逻辑

一、弹窗和弹窗组件

抱歉,这里我偷懒了直接用了elementUI的el-popover组件,所以小伙伴不懂的直接看elementUI官网的说明。

我个人也研究和编写了这块的组件功能(基于popper.js)

二、编写基础结构

这块属于html的基础内容,也就标题党了

三、canvas绘制图片

1、canvas绘制外部img图片

代码:


let mainDom = document.querySelector("#codeImg");
let bg = mainDom.getContext("2d");
let width = mainDom.width;
let height = mainDom.height;

let blockDom = document.querySelector("#sliderBlock");
let block = blockDom.getContext("2d");
//重新赋值,让canvas进行重新绘制
blockDom.height = height;
mainDom.height = height;

let imgsrc = require("../assets/images/back.jpg");
let img = document.createElement("img");
img.style.objectFit = "scale-down";
img.src = imgsrc;
img.onload = function() {
 bg.drawImage(img, 0, 0, width, height);
 block.drawImage(img, 0, 0, width, height);
};

这里我绘制了两个canvas,因为一个是背景一个是滑块

核心在于


let mainDom = document.querySelector("#codeImg");
let imgsrc = require("../assets/images/back.jpg");
let bg = mainDom.getContext("2d");
let img = document.createElement("img");
img.onload = function() {
 bg.drawImage(img, 0, 0, width, height);
};

2、canvas绘制滑块部分

就是这个图,这个有一些知识点,不难,但是很复杂。

具体请看:https://www.w3school.com.cn/tags/html_ref_canvas.asp

 

代码部分:


drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) {
 let x = xy.x,
 y = xy.y,
 r = xy.r,
 w = 40;
 let PI = Math.PI;
 //绘制
 ctx.beginPath();
 //left
 ctx.moveTo(x, y);
 //top
 ctx.arc(x + (w + 5) / 2, y, r, -PI, 0, true);
 ctx.lineTo(x + w + 5, y);
 //right
 ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false);
 ctx.lineTo(x + w + 5, y + w);
 //bottom
 ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false);
 ctx.lineTo(x, y + w);
 ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true);
 ctx.lineTo(x, y);
 //修饰,没有会看不出效果
 ctx.lineWidth = 1;
 ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
 ctx.strokeStyle = "rgba(255, 255, 255, 0.5)";
 ctx.stroke();
 ctx[type]();
 ctx.globalCompositeOperation = "xor";
}

解释下:

参数是传入canvas对象

x,y轴数据

剪切还是填充的canvas函数(fill,clip)

绘制难点:(很重要,不然你没法理解它怎么绘制的)

绘制主要是需要理解这里的绘制是根据你设置一个起始点坐标,然后你绘制第二次的时候线就会连接到第二个点,依次连接最后回到原点就形成一个完整的图形。

圆形的绘制:来自:https://www.w3school.com.cn/tags/canvas_arc.asp

 

用的是arc参数,主要是看这个图

fill是用于填充绘制的部分,clip是裁剪出绘制的部分,利用这个就可以出现一个扣掉的图片和一个裁剪出来的图片。

完成之后就是我的那个函数了。大家可以直接拿去用。

3、让元素跟随鼠标点击之后滑动

这里其实原理非常简单,就是有一个注意点。

原理:

鼠标点击之后记录当前坐标,然后随着( mousemove )滚动的时候修改元素的left和top值就行了。

还有一点就是鼠标快速滑动会导致丢失滑动效果,这里需要用document,不能是元素级别的监听。

元素上面我只需要鉴定按下 mousedown

代码:


//鼠标按下
drag(e) {
 console.log("鼠标按下", e);
 let dom = e.target; //dom元素
 let slider = document.querySelector("#sliderBlock"); //滑块dom
 const downCoordinate = { x: e.x, y: e.y };

 //正确的滑块数据
 let checkx = Number(this.slider.mx) - Number(this.slider.bx);
 //x轴数据
 let x = 0;
 const move = moveEV => {
 x = moveEV.x - downCoordinate.x;
 //y = moveEV.y - downCoordinate.y;
 if (x >= 251 || x <= 0) return false;
 dom.style.left = x + "px";
 //dom.style.top = y + "px";
 slider.style.left = x + "px";
 };

 const up = () => {
 document.removeEventListener("mousemove", move);
 document.removeEventListener("mouseup", up);
 dom.style.left = "";

 console.log(x, checkx);
 let max = checkx - 5;
 let min = checkx - 10;
 //允许正负误差1
 if ((max >= x && x >= min) || x === checkx) {
 console.log("滑动解锁成功");
 this.puzzle = true;
 this.tips = "验证成功";
 setTimeout(() => {
 this.visible = false;
 }, 500);
 } else {
 console.log("拼图位置不正确");
 this.tips = "验证失败,请重试";
 this.puzzle = false;
 this.canvasInit();
 }
 };

 document.addEventListener("mousemove", move);
 document.addEventListener("mouseup", up);
}

4、总结

核心点比较多,写过之后发现不难,关键在于去写

个人该页面git地址:https://github.com/ht-sauce/dream

该页面处于项目的

 

路由访问为:http://localhost:8080/consumer

5、完整的页面代码


<template> <div id="login">
 <el-form class="loginFrom" :model="logindata" :rules="rules" ref="ruleForm">
 <el-form-item class="login-item">
 <h1 class="login-title">海天酱油登录中心</h1>
 </el-form-item>
 <el-form-item prop="userName">
 <el-input
 class="login-inputorbuttom"
 prefix-icon="el-icon-user"
 placeholder="登录名"
 v-model="logindata.userName"
 ></el-input>
 </el-form-item>
 <el-form-item prop="password">
 <el-input
 class="login-inputorbuttom"
 prefix-icon="el-icon-lock"
 placeholder="密码"
 v-model="logindata.password"
 ></el-input>
 </el-form-item>
 <!--<el-form-item prop="verificationCode">
 <el-input
 class="login-inputorbuttom"
 v-model="logindata.verificationCode"
 ></el-input>
 </el-form-item>-->
 <el-form-item class="login-item">
 <el-button
 class="login-inputorbuttom login-bottom"
 type="primary"
 v-popover:popover
 @click="visible = !visible"
 >登 录</el-button
 >
 </el-form-item>
 </el-form>
 <!--验证码弹窗-->
 <el-popover
 popper-class="slidingPictures"
 ref="popover"
 trigger="manual"
 v-model="visible"
 >
 <div class="sliding-pictures">
 <div class="vimg">
 <canvas id="sliderBlock"></canvas>
 <canvas id="codeImg"></canvas>
 </div>
 <div class="slider">
 <div class="track" :class="{ pintuTrue: puzzle }">
 {{ tips }}
 </div>
 <div class="button el-icon-s-grid" @mousedown.prevent="drag"></div>
 </div>
 <div class="operation">
 <span
 title="关闭验证码"
 @click="visible = false"
 class="el-icon-circle-close"
 ></span>
 <span
 title="刷新验证码"
 @click="canvasInit"
 class="el-icon-refresh-left"
 ></span>
 </div>
 </div>
 </el-popover>
 </div>
</template>
<script>
export default {
 name: "login",
 data() {
 return {
 tips: "拖动左边滑块完成上方拼图",
 logindata: {
 userName: "",
 password: "",
 verificationCode: ""
 },
 rules: {},
 visible: false,
 //滑块x轴数据
 slider: {
 mx: 0,
 bx: 0
 },
 //拼图是否正确
 puzzle: false
 };
 },
 watch: {
 visible(e) {
 if (e === true) {
 this.canvasInit();
 this.puzzle = false;
 }
 }
 },
 beforeCreate() {},
 created() {},
 beforeMount() {},
 mounted() {},
 methods: {
 //拼图验证码初始化
 canvasInit() {
 //生成指定区间的随机数
 const random = (min, max) => {
 return Math.floor(Math.random() * (max - min + 1) + min);
 };
 //x: 254, y: 109
 let mx = random(127, 244),
 bx = random(10, 128),
 y = random(10, 99);
 this.slider = { mx, bx };
 this.draw(mx, bx, y);
 },
 //鼠标按下
 drag(e) {
 console.log("鼠标按下", e);
 let dom = e.target; //dom元素
 let slider = document.querySelector("#sliderBlock"); //滑块dom
 const downCoordinate = { x: e.x, y: e.y };
 //正确的滑块数据
 let checkx = Number(this.slider.mx) - Number(this.slider.bx);
 //x轴数据
 let x = 0;
 const move = moveEV => {
 x = moveEV.x - downCoordinate.x;
 //y = moveEV.y - downCoordinate.y;
 if (x >= 251 || x <= 0) return false;
 dom.style.left = x + "px";
 //dom.style.top = y + "px";
 slider.style.left = x + "px";
 };
 const up = () => {
 document.removeEventListener("mousemove", move);
 document.removeEventListener("mouseup", up);
 dom.style.left = "";
 console.log(x, checkx);
 let max = checkx - 5;
 let min = checkx - 10;
 //允许正负误差1
 if ((max >= x && x >= min) || x === checkx) {
 console.log("滑动解锁成功");
 this.puzzle = true;
 this.tips = "验证成功";
 setTimeout(() => {
 this.visible = false;
 }, 500);
 } else {
 console.log("拼图位置不正确");
 this.tips = "验证失败,请重试";
 this.puzzle = false;
 this.canvasInit();
 }
 };
 document.addEventListener("mousemove", move);
 document.addEventListener("mouseup", up);
 },
 draw(mx = 200, bx = 20, y = 50) {
 let mainDom = document.querySelector("#codeImg");
 let bg = mainDom.getContext("2d");
 let width = mainDom.width;
 let height = mainDom.height;
 let blockDom = document.querySelector("#sliderBlock");
 let block = blockDom.getContext("2d");
 //重新赋值,让canvas进行重新绘制
 blockDom.height = height;
 mainDom.height = height;
 let imgsrc = require("../assets/images/back.jpg");
 let img = document.createElement("img");
 img.style.objectFit = "scale-down";
 img.src = imgsrc;
 img.onload = function() {
 bg.drawImage(img, 0, 0, width, height);
 block.drawImage(img, 0, 0, width, height);
 };
 let mainxy = { x: mx, y: y, r: 9 };
 let blockxy = { x: bx, y: y, r: 9 };
 this.drawBlock(bg, mainxy, "fill");
 this.drawBlock(block, blockxy, "clip");
 },
 //绘制拼图
 drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) {
 let x = xy.x,
 y = xy.y,
 r = xy.r,
 w = 40;
 let PI = Math.PI;
 //绘制
 ctx.beginPath();
 //left
 ctx.moveTo(x, y);
 //top
 ctx.arc(x + (w + 5) / 2, y, r, -PI, 0, true);
 ctx.lineTo(x + w + 5, y);
 //right
 ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false);
 ctx.lineTo(x + w + 5, y + w);
 //bottom
 ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false);
 ctx.lineTo(x, y + w);
 ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true);
 ctx.lineTo(x, y);
 //修饰,没有会看不出效果
 ctx.lineWidth = 1;
 ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
 ctx.strokeStyle = "rgba(255, 255, 255, 0.5)";
 ctx.stroke();
 ctx[type]();
 ctx.globalCompositeOperation = "xor";
 }
 }
};
</script>
<style>
.slidingPictures {
 padding: 0;
 width: 300px;
 border-radius: 2px;
}
</style>
<style scoped lang="scss">
#login {
 display: flex;
 flex-flow: row;
 justify-content: flex-end;
 align-items: center;
 width: 100%;
 height: 100%;
 background-image: url("../assets/images/back.jpg");
 background-size: 100% 100%;
 .loginFrom {
 width: 300px;
 margin-top: -10vw;
 margin-right: 10vw;
 .login-item {
 display: flex;
 justify-content: center;
 align-items: center;
 }
 .login-title {
 color: #ffffff;
 font-size: 16px;
 margin-bottom: 10px;
 }
 .login-bottom {
 margin-top: 15px;
 }
 .login-bottom:hover {
 background: rgba(28, 136, 188, 0.5);
 }
 .login-bottom:active {
 background: rgba(228, 199, 200, 0.5);
 }
 /deep/.login-inputorbuttom {
 height: 40px;
 width: 300px;
 background: rgba(57, 108, 158, 0.5);
 border-radius: 20px;
 border: #396c9e 1px solid;
 font-size: 14px;
 color: #ffffff;
 .el-input--small,
 .el-input__inner {
 line-height: 43px;
 border: none;
 color: #ffffff;
 font-size: 14px;
 height: 40px;
 border-radius: 20px;
 background: transparent;
 text-align: center;
 }
 .el-input__icon {
 line-height: 40px;
 font-size: 16px;
 }
 }
 }
}
/*该样式最终是以弹窗插入*/
.sliding-pictures {
 width: 100%;
 .vimg {
 width: 100%;
 height: 170px;
 #codeImg,
 #sliderBlock {
 padding: 7px 7px 0 7px;
 width: inherit;
 height: inherit;
 }
 #codeImg {
 //display: none;
 }
 #sliderBlock {
 position: absolute;
 z-index: 4000;
 }
 }
 .slider {
 width: 100%;
 height: 65px;
 border-bottom: #c7c9d0 1px solid;
 display: flex;
 align-items: center;
 justify-content: flex-start;
 .track {
 margin-left: 7px;
 width: 286px;
 height: 38px;
 background: rgba(28, 136, 188, 0.5);
 border-radius: 25px;
 font-size: 14px;
 line-height: 38px;
 padding-right: 15px;
 padding-left: 70px;
 }
 .pintuTrue {
 background: #67c23a;
 color: #ffffff;
 }
 .button {
 position: absolute;
 width: 50px;
 height: 50px;
 line-height: 48px;
 background: #ffffff;
 box-shadow: #b9bdc8 0 0 3px;
 border-radius: 50%;
 left: 7px;
 text-align: center;
 font-size: 28px;
 color: #3e5d8b;
 &:hover {
 color: #2181bd;
 }
 }
 }
 .operation {
 width: 100%;
 height: 40px;
 > span {
 color: #9fa3ac;
 display: inline-block;
 width: 40px;
 font-size: 25px;
 line-height: 40px;
 text-align: center;
 &:hover {
 background: #e2e8f5;
 }
 }
 }
}
</style>

总结

以上所述是小编给大家介绍的Vue实现滑动拼图验证码功能,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小会及时回复大家的!

vue 拼图验证码功能 vue 验证码