Vue 登录验证之滑动拼图验证码

滑动登录验证码

登录验证码
登录时实现滑动登录验证需要一些步骤:

  1. 弹窗功能
  2. 弹窗基于元素定位
  3. 元素拖动
  4. canvas绘图
  5. 基础逻辑

1. 弹窗和弹窗组件

使用elementUI的el-popover组件,可以看elementUI官网的说明。

2. Canvas绘制图片

  • 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);
};
  • canvas绘制滑块部分

滑动拼图验证码
代码:

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)

绘制难点:(很重要,不然你没法理解它怎么绘制的)

绘制主要是需要理解这里的绘制是根据你设置一个起始点坐标,然后你绘制第二次的时候线就会连接到第二个点,依次连接最后回到原点就形成一个完整的图形。

Vue实现滑动拼图验证码
用的是arc参数,主要是看这个图

fill是用于填充绘制的部分,clip是裁剪出绘制的部分,利用这个就可以出现一个扣掉的图片和一个裁剪出来的图片。

完成之后就是我的那个函数了。大家可以直接拿去用。

  • 让元素跟随鼠标点击之后滑动

原理:

鼠标点击之后记录当前坐标,然后随着(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);
}
  • 完整的页面代码

    <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>
    
  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值