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 相关文章推荐
用JavaScript和注册表脚本实现右键收藏Web页选中文本
Jan 28 Javascript
文本域光标操作的jQuery扩展分享
Mar 10 Javascript
js判断登录与否并确定跳转页面的方法
Jan 30 Javascript
Lab.js初次使用笔记
Feb 28 Javascript
js根据手机客户端浏览器类型,判断跳转官网/手机网站多个实例代码
Apr 30 Javascript
Three.js快速入门教程
Sep 09 Javascript
浅谈js函数三种定义方式 &amp; 四种调用方式 &amp; 调用顺序
Feb 19 Javascript
Vue2.0 UI框架ElementUI使用方法详解
Apr 14 Javascript
运用jQuery写的验证表单(实例讲解)
Jul 06 jQuery
AngularJS日程表案例详解
Aug 15 Javascript
vue的常用组件操作方法应用分析
Apr 13 Javascript
vue使用Element组件时v-for循环里的表单项验证方法
Jun 28 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
PHP中的超全局变量
2006/10/09 PHP
用PHP的超级变量$_POST获取HTML表单(HTML Form) 数据
2011/05/07 PHP
PHP版 汉字转码的实现详解
2013/06/09 PHP
PHP获取用户客户端真实IP的解决方案
2016/10/10 PHP
VSCode+PHPstudy配置PHP开发环境的步骤详解
2020/08/20 PHP
JavaScript 密码强度判断代码
2009/09/05 Javascript
PPK 谈 JavaScript 的 this 关键字 [翻译]
2009/09/29 Javascript
jQuery EasyUI API 中文文档 - PropertyGrid属性表格
2011/11/18 Javascript
MyEclipse取消验证Js的两种方法
2013/11/14 Javascript
js实现仿京东2级菜单效果(带延时功能)
2015/08/27 Javascript
javascript中递归的两种写法
2017/01/17 Javascript
Vue.js使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用
2017/05/10 Javascript
js模拟百度模糊搜索的实例
2017/08/04 Javascript
解决vue-router进行build无法正常显示路由页面的问题
2018/03/06 Javascript
20个最常见的jQuery面试问题及答案
2018/05/23 jQuery
ES6基础之 Promise 对象用法实例详解
2019/08/22 Javascript
小程序如何写动态标签的实现方法
2020/02/05 Javascript
使用nodejs实现JSON文件自动转Excel的工具(推荐)
2020/06/24 NodeJs
vue使用axios实现excel文件下载的功能
2020/07/16 Javascript
js对象属性名驼峰式转下划线的实例代码
2020/09/17 Javascript
[01:06:19]DOTA2-DPC中国联赛定级赛 LBZS vs SAG BO3第二场 1月8日
2021/03/11 DOTA
python队列通信:rabbitMQ的使用(实例讲解)
2017/12/22 Python
TensorFlow实现iris数据集线性回归
2018/09/07 Python
Python批处理更改文件名os.rename的方法
2018/10/26 Python
Python+pyplot绘制带文本标注的柱状图方法
2019/07/08 Python
django之静态文件 django 2.0 在网页中显示图片的例子
2019/07/28 Python
Win10系统下安装labelme及json文件批量转化方法
2019/07/30 Python
tensorflow dataset.shuffle、dataset.batch、dataset.repeat顺序区别详解
2020/06/03 Python
keras实现多种分类网络的方式
2020/06/11 Python
CSS3实现大小不一的粒子旋转加载动画
2016/04/21 HTML / CSS
硅酸盐工业控制专业应届生求职信
2013/11/02 职场文书
《蜗牛的奖杯》教后反思
2014/04/24 职场文书
家长会主持词开场白
2015/05/29 职场文书
让世界充满爱观后感
2015/06/10 职场文书
暂住证明怎么写
2015/06/19 职场文书
java中重写父类方法加不加@Override详解
2021/06/21 Java/Android