Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能

緣由:之前看嗶哩嗶哩官網(wǎng)登錄的時(shí)候有一個(gè)拼圖驗(yàn)證碼,很好奇怎么去實(shí)現(xiàn)。然后就想著自己弄一個(gè)。先給大家看我的最終效果。后面再一點(diǎn)點(diǎn)拆解代碼。

成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供江岸網(wǎng)站建設(shè)、江岸做網(wǎng)站、江岸網(wǎng)站設(shè)計(jì)、江岸網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、江岸企業(yè)網(wǎng)站模板建站服務(wù),10余年江岸做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。

Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能 

為什么想著寫這個(gè)功能呢,主要在于拼圖驗(yàn)證碼在前端這里會(huì)比較復(fù)雜并且深入。相比文字拼寫,12306的圖片驗(yàn)證碼都沒有拼圖驗(yàn)證碼對(duì)前端的要求來的復(fù)雜,和難。

我總結(jié)下知識(shí)點(diǎn):

1、彈窗功能

2、彈窗基于元素定位

3、元素拖動(dòng)

4、canvas繪圖

5、基礎(chǔ)邏輯

一、彈窗和彈窗組件

抱歉,這里我偷懶了直接用了elementUI的el-popover組件,所以小伙伴不懂的直接看elementUI官網(wǎng)的說明。

我個(gè)人也研究和編寫了這塊的組件功能(基于popper.js)

二、編寫基礎(chǔ)結(jié)構(gòu)

這塊屬于html的基礎(chǔ)內(nèi)容,也就標(biāo)題黨了

三、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進(jìn)行重新繪制
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);
};

這里我繪制了兩個(gè)canvas,因?yàn)橐粋€(gè)是背景一個(gè)是滑塊

核心在于

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繪制滑塊部分

就是這個(gè)圖,這個(gè)有一些知識(shí)點(diǎn),不難,但是很復(fù)雜。

具體請(qǐng)看:https://www.w3school.com.cn/tags/html_ref_canvas.asp

Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能 

代碼部分:

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);
 //修飾,沒有會(huì)看不出效果
 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";
}

解釋下:

參數(shù)是傳入canvas對(duì)象

x,y軸數(shù)據(jù)

剪切還是填充的canvas函數(shù)(fill,clip)

繪制難點(diǎn):(很重要,不然你沒法理解它怎么繪制的)

繪制主要是需要理解這里的繪制是根據(jù)你設(shè)置一個(gè)起始點(diǎn)坐標(biāo),然后你繪制第二次的時(shí)候線就會(huì)連接到第二個(gè)點(diǎn),依次連接最后回到原點(diǎn)就形成一個(gè)完整的圖形。

圓形的繪制:來自:https://www.w3school.com.cn/tags/canvas_arc.asp

Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能 

用的是arc參數(shù),主要是看這個(gè)圖

fill是用于填充繪制的部分,clip是裁剪出繪制的部分,利用這個(gè)就可以出現(xiàn)一個(gè)扣掉的圖片和一個(gè)裁剪出來的圖片。

完成之后就是我的那個(gè)函數(shù)了。大家可以直接拿去用。

3、讓元素跟隨鼠標(biāo)點(diǎn)擊之后滑動(dòng)

這里其實(shí)原理非常簡(jiǎn)單,就是有一個(gè)注意點(diǎn)。

原理:

鼠標(biāo)點(diǎn)擊之后記錄當(dāng)前坐標(biāo),然后隨著( mousemove )滾動(dòng)的時(shí)候修改元素的left和top值就行了。

還有一點(diǎn)就是鼠標(biāo)快速滑動(dòng)會(huì)導(dǎo)致丟失滑動(dòng)效果,這里需要用document,不能是元素級(jí)別的監(jiān)聽。

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

代碼:

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

 //正確的滑塊數(shù)據(jù)
 let checkx = Number(this.slider.mx) - Number(this.slider.bx);
 //x軸數(shù)據(jù)
 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;
 //允許正負(fù)誤差1
 if ((max >= x && x >= min) || x === checkx) {
  console.log("滑動(dòng)解鎖成功");
  this.puzzle = true;
  this.tips = "驗(yàn)證成功";
  setTimeout(() => {
  this.visible = false;
  }, 500);
 } else {
  console.log("拼圖位置不正確");
  this.tips = "驗(yàn)證失敗,請(qǐng)重試";
  this.puzzle = false;
  this.canvasInit();
 }
 };

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

4、總結(jié)

核心點(diǎn)比較多,寫過之后發(fā)現(xiàn)不難,關(guān)鍵在于去寫

個(gè)人該頁面git地址:https://github.com/ht-sauce/dream

該頁面處于項(xiàng)目的

Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能 

路由訪問為: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">
  <h2 class="login-title">海天醬油登錄中心</h2>
  </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>
 <!--驗(yàn)證碼彈窗-->
 <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="關(guān)閉驗(yàn)證碼"
   @click="visible = false"
   class="el-icon-circle-close"
   ></span>
   <span
   title="刷新驗(yàn)證碼"
   @click="canvasInit"
   class="el-icon-refresh-left"
   ></span>
  </div>
  </div>
 </el-popover>
 </div>
</template>
<script>
export default {
 name: "login",
 data() {
 return {
  tips: "拖動(dòng)左邊滑塊完成上方拼圖",
  logindata: {
  userName: "",
  password: "",
  verificationCode: ""
  },
  rules: {},
  visible: false,
  //滑塊x軸數(shù)據(jù)
  slider: {
  mx: 0,
  bx: 0
  },
  //拼圖是否正確
  puzzle: false
 };
 },
 watch: {
 visible(e) {
  if (e === true) {
  this.canvasInit();
  this.puzzle = false;
  }
 }
 },
 beforeCreate() {},
 created() {},
 beforeMount() {},
 mounted() {},
 methods: {
 //拼圖驗(yàn)證碼初始化
 canvasInit() {
  //生成指定區(qū)間的隨機(jī)數(shù)
  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);
 },
 //鼠標(biāo)按下
 drag(e) {
  console.log("鼠標(biāo)按下", e);
  let dom = e.target; //dom元素
  let slider = document.querySelector("#sliderBlock"); //滑塊dom
  const downCoordinate = { x: e.x, y: e.y };
  //正確的滑塊數(shù)據(jù)
  let checkx = Number(this.slider.mx) - Number(this.slider.bx);
  //x軸數(shù)據(jù)
  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;
  //允許正負(fù)誤差1
  if ((max >= x && x >= min) || x === checkx) {
   console.log("滑動(dòng)解鎖成功");
   this.puzzle = true;
   this.tips = "驗(yàn)證成功";
   setTimeout(() => {
   this.visible = false;
   }, 500);
  } else {
   console.log("拼圖位置不正確");
   this.tips = "驗(yàn)證失敗,請(qǐng)重試";
   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進(jìn)行重新繪制
  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);
  //修飾,沒有會(huì)看不出效果
  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>

總結(jié)

以上所述是小編給大家介紹的Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小會(huì)及時(shí)回復(fù)大家的!

新聞名稱:Vue實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能
標(biāo)題來源:http://bm7419.com/article30/jdedpo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷、微信小程序、品牌網(wǎng)站設(shè)計(jì)、商城網(wǎng)站、營(yíng)銷型網(wǎng)站建設(shè)軟件開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司