Reason: bilibili official website login before there is a puzzle verification code, I am curious how to achieve. Then I thought I’d get my own. Let me show you my final result. I’m going to break down the code a little bit.

The reason why I wanted to write this feature is because the puzzle captcha is a little bit more complicated and deep at the front end. Compared to text spelling, 12306’s picture captcha is not as complex and difficult as the front-end requirements of the jigsaw captcha.

Let me summarize the points:

1. Popover function

2. Popovers are positioned based on elements

3. Element drag

4. Canvas Drawing

5. Basic logic

First, popover and popover components

I’m sorry, but I used elementUI’s el-popover component directly, so if you don’t understand it, you can read elementUI’s official website.

I personally researched and wrote this component functionality (based on popper.js)

Second, write the infrastructure

This is the basic content of HTML, which is the clicker

3. Drawing pictures on canvas

1. Canvas draws external IMG images

Code:

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"); // Reassign to canvas to redraw 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); };Copy the code

Here I draw two canvases because one is the background and one is the slider

The core is

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); };Copy the code

2. Canvas draws the slider part

This is the graph, this is a little bit of an idea, not hard, but very complicated.

Specific look at: www.w3school.com.cn/tags/html\_…

Code part:

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; / / draw the 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); 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"; }Copy the code

Explain:

The argument is passed to the Canvas object

X and y data

Clip or fill canvas function (fill, clip)

Difficulty in drawing :(very important, otherwise you won’t understand how it is drawn)

The main thing to understand about drawing is that what you’re doing here is you’re setting up a starting point, and then when you draw it a second time it’s going to connect to a second point, and then it’s going to connect back to the origin and it’s going to be a complete shape.

Round draw: from: www.w3school.com.cn/tags/canvas…

Using the ARC parameter, mainly looking at this diagram

Fill is used to fill the drawing part and clip is used to clip the drawing part. With this, a clipped image and a clipped image can appear.

And then it’s my function. You can just take it and use it.

3. Let the element slide with the mouse click

The principle here is actually very simple, there is a point of attention.

Principle:

Record the current coordinates after the mouse click, and then change the left and top values of the element as you scroll.

Another point is that a quick mouse slide will lose the slide effect, so you need document, not element level listening.

The element above I just need to identify by pressing Mousedown

Code:

// Drag (e) {console.log(" mouse down ", e); let dom = e.target; //dom element let slider = document.querySelector("#sliderBlock"); // Dom const downCoordinate = {x: e.x, y: e.y}; Let checkx = Number(this.slider.mx) - Number(this.slider.bx); // 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; / / allow the error of plus or minus 1 if ((Max > = x && x > = min) | | x = = = checkx) {the console. The log (" sliding unlock success "); this.puzzle = true; This. tips = "Verify successful "; setTimeout(() => { this.visible = false; }, 500); } else {console.log(" puzzle position not correct "); This. tips = "Validation failed, please try again "; this.puzzle = false; this.canvasInit(); }}; document.addEventListener("mousemove", move); document.addEventListener("mouseup", up); }Copy the code

4, summarize

The core point is more, after writing it is not difficult to find, the key is to write

Git address: github.com/ht-sauce/dr…

The page is in the project’s

Routing access is: http://localhost:8080/consumer

5. Complete page code

<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>
Copy the code

6, thanks

My code reference comes from:

www.jb51.net/article/137…

Author’s git address: github.com/yeild/jigsa…

Touch the fish:

Touch the fish: 797957786,