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 相关文章推荐
node.js中的fs.symlink方法使用说明
Dec 15 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
Jul 07 Javascript
ES6所改良的javascript“缺陷”问题
Aug 23 Javascript
javascript如何创建对象
Aug 29 Javascript
js获取ip和地区
Mar 10 Javascript
Angular4学习之Angular CLI的安装与使用教程
Jan 04 Javascript
JS非行间样式获取函数的实例代码
Jun 05 Javascript
JS拖动选择table里的单元格完整实例【基于jQuery】
May 28 jQuery
详解基于原生JS验证表单组件xy-form
Aug 20 Javascript
jquery 遍历hash操作示例【基于ajax交互】
Oct 12 jQuery
JS实现canvas简单小画板功能
Jun 23 Javascript
javascript实现评分功能
Jun 24 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 error_log 函数的使用
2009/04/13 PHP
PHP使用curl函数发送Post请求的注意事项
2016/11/26 PHP
PHP+ajax实现获取新闻数据简单示例
2018/05/08 PHP
PHP call_user_func和call_user_func_array函数的简单理解与应用分析
2019/11/25 PHP
FormValidate 表单验证功能代码更新并提供下载
2008/08/23 Javascript
基于jquery的loading效果实现代码
2010/11/05 Javascript
细说浏览器特性检测(2)-通用事件检测
2010/11/05 Javascript
jquery实现标签支持图文排列带上下箭头按钮的选项卡
2015/03/14 Javascript
JavaScript中Function()函数的使用教程
2015/06/04 Javascript
QQ登录背景闪动效果附效果演示源码下载
2015/09/22 Javascript
JavaScript多并发问题如何处理
2015/10/28 Javascript
JQuery 的跨域方法推荐_可跨任何网站
2016/05/18 Javascript
JavaScript中的this使用详解
2016/07/27 Javascript
JavaScript之WebSocket技术详解
2016/11/18 Javascript
浅谈js函数的多种定义方法与区别
2016/11/29 Javascript
浅谈使用splice函数对数组中的元素进行删除时的注意事项
2016/12/04 Javascript
使用JavaScript触发过渡效果的方法
2017/01/19 Javascript
Vue2.0 组件传值通讯的示例代码
2017/08/01 Javascript
百度地图去掉marker覆盖物或者去掉maker的label文字方法
2018/01/26 Javascript
小程序实现多列选择器
2019/02/15 Javascript
如何在JavaScript中优雅的提取循环内数据详解
2019/03/04 Javascript
浅谈js数组splice删除某个元素爬坑
2020/10/14 Javascript
[00:32]2018DOTA2亚洲邀请赛OpTic出场
2018/04/03 DOTA
Python中处理字符串之endswith()方法的使用简介
2015/05/18 Python
Python爬虫之模拟知乎登录的方法教程
2017/05/25 Python
python 匹配url中是否存在IP地址的方法
2018/06/04 Python
深入了解Python在HDA中的应用
2019/09/05 Python
python [:3] 实现提取数组中的数
2019/11/27 Python
在Python中用GDAL实现矢量对栅格的切割实例
2020/03/11 Python
《草原的早晨》教学反思
2014/04/08 职场文书
生态养殖创业计划书
2014/05/06 职场文书
欠款起诉书范文
2015/05/19 职场文书
家装电话营销开场白
2015/05/29 职场文书
让子弹飞观后感
2015/06/11 职场文书
2015教师个人师德工作总结
2015/10/23 职场文书
PHP 时间处理类Carbon
2022/05/20 PHP