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系列之Javascript基础篇
Jun 07 Javascript
js移除事件 js绑定事件实例应用
Nov 28 Javascript
Javascript中的异步编程规范Promises/A详细介绍
Jun 06 Javascript
JS实现仿中关村论坛评分后弹出提示效果的方法
Feb 23 Javascript
JavaScript的面向对象编程基础
Aug 13 Javascript
javascript基础语法学习笔记
Jan 04 Javascript
javascript对浅拷贝和深拷贝的详解
Oct 14 Javascript
JavaScript将base64图片转换成formData并通过AJAX提交的实现方法
Oct 24 Javascript
react开发教程之React 组件之间的通信方式
Aug 12 Javascript
Vue.js样式动态绑定实现小结
Jan 24 Javascript
Vue路由对象属性 .meta $route.matched详解
Nov 04 Javascript
详解vue父子组件状态同步的最佳方式
Sep 10 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-accelerator网站加速PHP缓冲的方法
2008/07/30 PHP
使用PHP生成二维码的两种方法(带logo图像)
2014/03/14 PHP
php实现的css文件背景图片下载器代码
2014/11/11 PHP
php字符串函数学习之substr()
2015/03/27 PHP
JavaScript 页面坐标相关知识整理
2010/01/09 Javascript
javascript新建标签,判断键盘输入,以及判断焦点(示例代码)
2013/11/25 Javascript
js 获取、清空input type=&quot;file&quot;的值(示例代码)
2013/12/24 Javascript
jquery中页面Ajax方法$.load的功能使用介绍
2014/10/20 Javascript
node.js解决获取图片真实文件类型的问题
2014/12/20 Javascript
JS+CSS实现弹出全屏灰黑色透明遮罩效果的方法
2014/12/20 Javascript
CheckBox多选取值及判断CheckBox选中是否为空的实例
2017/10/31 Javascript
微信小程序实现打开内置地图功能【附源码下载】
2017/12/07 Javascript
vue中el-upload上传图片到七牛的示例代码
2018/10/19 Javascript
Javascript删除数组里的某个元素
2019/02/28 Javascript
js使用cookie实现记住用户名功能示例
2019/06/13 Javascript
vue 遮罩层阻止默认滚动事件操作
2020/07/28 Javascript
Python的字典和列表的使用中一些需要注意的地方
2015/04/24 Python
详解python调度框架APScheduler使用
2017/03/28 Python
Django查询数据库的性能优化示例代码
2017/09/24 Python
Python3实现发送QQ邮件功能(文本)
2017/12/15 Python
Python中property函数用法实例分析
2018/06/04 Python
pycharm执行python时,填写参数的方法
2018/10/29 Python
win10环境下配置vscode python开发环境的教程详解
2019/10/16 Python
Python Numpy 自然数填充数组的实现
2019/11/28 Python
一款基于css3和jquery实现的动画显示弹出层按钮教程
2015/01/04 HTML / CSS
10分钟入门CSS3 Animation
2018/12/25 HTML / CSS
倩碧英国官网:Clinique英国
2018/08/10 全球购物
保加利亚服装和鞋类购物网站:Bibloo.bg
2020/11/08 全球购物
关于幼儿的自我评价
2013/12/18 职场文书
心理健康教育制度
2014/01/27 职场文书
工厂采购员岗位职责
2014/04/08 职场文书
长城英文导游词
2015/01/30 职场文书
催款函怎么写
2015/06/24 职场文书
深入理解以DEBUG方式线程的底层运行原理
2021/06/21 Java/Android
python三子棋游戏
2022/05/04 Python
Python实现双向链表
2022/05/25 Python