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


Posted in Javascript onSeptember 15, 2019

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

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

为什么想着写这个功能呢,主要在于拼图验证码在前端这里会比较复杂并且深入。相比文字拼写,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

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

代码部分:

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

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

用的是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

该页面处于项目的

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

路由访问为: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实现滑动拼图验证码功能,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小会及时回复大家的!

Javascript 相关文章推荐
jquery下利用jsonp跨域访问实现方法
Jul 29 Javascript
ExtJS 入门
Oct 29 Javascript
浅谈jQuery中对象遍历.eq().first().last().slice()方法
Nov 26 Javascript
使用JQuery选择HTML遍历函数的方法
Sep 17 Javascript
Bootstrap Navbar Component实现响应式导航
Oct 08 Javascript
原生JS轮播图插件
Feb 09 Javascript
Node错误处理笔记之挖坑系列教程
Jun 05 Javascript
JSON基本语法及与JavaScript的异同实例分析
Jan 04 Javascript
mpvue全局引入sass文件的方法步骤
Mar 06 Javascript
jquery实现的分页显示功能示例
Aug 23 jQuery
layui给下拉框、按钮状态、时间赋初始值的方法
Sep 10 Javascript
浅谈vue生命周期共有几个阶段?分别是什么?
Aug 07 Javascript
Vue 利用指令实现禁止反复发送请求的两种方法
Sep 15 #Javascript
解决layui调用自定义方法提示未定义的问题
Sep 14 #Javascript
layui使用label标签的方法
Sep 14 #Javascript
使用layui定义一个模块并使用的例子
Sep 14 #Javascript
基于Layui自定义模块的使用方法详解
Sep 14 #Javascript
解决layui的form里的元素进行动态生成,验证失效的问题
Sep 14 #Javascript
浅谈layui使用模板引擎动态渲染元素要注意的问题
Sep 14 #Javascript
You might like
VOLVO车载收音机
2021/03/02 无线电
PHP购物车类Cart.class.php定义与用法示例
2016/07/20 PHP
php+mysql+jquery实现简易的检索自动补全提示功能
2017/04/15 PHP
解决在laravel中auth建立时候遇到的问题
2019/10/15 PHP
Js 订制自己的AlertBox(信息提示框)
2009/01/09 Javascript
基于jquery步骤进度条源码分享
2015/11/12 Javascript
jQueryUI DatePicker 添加时分秒
2016/06/04 Javascript
Bootstrap组件系列之福利篇几款好用的组件(推荐)
2016/06/23 Javascript
D3.js中强制异步文件读取同步的几种方法
2017/02/06 Javascript
jquery.onoff实现简单的开关按钮功能(推荐)
2018/05/24 jQuery
Bootbox将后台JSON数据填充Form表单的实例代码
2018/09/10 Javascript
简述pm2常用命令集合及配置文件说明
2019/05/30 Javascript
微信小程序webview 脚手架使用详解
2019/07/22 Javascript
layui 点击重置按钮, select 并没有被重置的解决方法
2019/09/03 Javascript
详解element-ui中表单验证的三种方式
2019/09/18 Javascript
vue项目出现页面空白的解决方案
2019/10/31 Javascript
JavaScript交换两个变量方法实例
2019/11/25 Javascript
JS 数组基本用法入门示例解析
2020/01/16 Javascript
vue $mount 和 el的区别说明
2020/09/11 Javascript
Python封装shell命令实例分析
2015/05/05 Python
python类继承用法实例分析
2015/05/27 Python
使用Python脚本实现批量网站存活检测遇到问题及解决方法
2016/10/11 Python
python中的闭包函数
2018/02/09 Python
Python拼接字符串的7种方法总结
2018/11/01 Python
python实现简易学生信息管理系统
2020/04/05 Python
Python解析json代码实例解析
2019/11/25 Python
python next()和iter()函数原理解析
2020/02/07 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
2020/12/21 Python
CSS3 边框效果
2019/11/04 HTML / CSS
中国综合性网上购物商城:当当(网上卖书起家)
2016/11/16 全球购物
Europcar葡萄牙:葡萄牙汽车和货车租赁
2017/10/13 全球购物
面向对象概念面试题(.NET)
2016/11/04 面试题
餐饮服务食品安全责任书
2014/07/25 职场文书
大学生读书笔记范文
2015/07/01 职场文书
vue.js Router中嵌套路由的实用示例
2021/06/27 Vue.js
一行Python命令实现批量加水印
2022/04/07 Python