Security verification

At many sites, at the time of sending message authentication code, let the user input graphic verification code, or drag the picture puzzle, or choose the tap, tap water from a few pictures or words on it in the picture, or a picture of a traffic light (I always choose wrong), or is also a picture of a warped spin to the right Angle. This article will talk about how to implement image rotation verification. By security verification, it means verifying that the user is a real person and not a robot.

Effect in

Picture and rotation Angle

Generally speaking, the back end should return a rotated picture, and the front end should turn the picture clockwise and return the back end an Angle of the front end rotation. The back end should add up its rotation Angle and the front end rotation Angle, and when the back end adds up its rotation Angle and the front end rotation Angle, it will indicate that the user rotation Angle is correct. Here’s why we want to use similar, because if you force 360 to be equal, it will be difficult for the user to complete the verification, so you need to justify a certain margin of error.

Because of the conditions of the article, it’s hard to show the back end, so let’s just take a picture that’s rotated 90 degrees, and then Angle check, and do it on the front end.

Building a basic layout

Because there are a lot of states, so I’ll write vue.

html

<div id="app">
  <div class="check">
    <p>Drag the slider to make the image Angle positive</p>
    <div class="img-con">
      <img src="https://z3.ax1x.com/2021/08/06/fn7X4S.png" />
    </div>
    <div ref="sliderCon" class="slider-con">
      <div ref="slider" id="slider" class="slider" :class="slider">
      </div>
    </div>
  </div>
</div>
Copy the code

css

#app {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.check {
  --slider-size: 50px;
  margin-top: 20px;
  width: 300px;
  background: white;
  box-shadow: 0 0 5px grey;
  border-radius: 5px;
  padding: 20px 0;

  display: flex;
  flex-direction: column;
  align-items: center;
}

.check .img-con {
  position: relative;
  overflow: hidden;
  width: 120px;
  height: 120px;
  border-radius: 50%;
}

.check .img-con img {
  width: 100%;
  height: 100%;
  user-select: none;
}

.check .slider-con {
  width: 80%;
  height: var(--slider-size);
  border-radius: 50px;
  margin-top: 1rem;
  position: relative;
  background: #f5f5f5;
  box-shadow: 0 0 5px rgba(0.0.0.0.1) inset;
}

.slider-con .slider {
  background: white;
  width: var(--slider-size);
  height: var(--slider-size);
  border-radius: 50%;
  box-shadow: 0 0 5px rgba(0.0.0.0.2);
  cursor: move;
}

body {
  padding: 0;
  margin: 0;
  background: #fef5e0;
}

Copy the code

js

Vue.createApp({
  data() {
    return{}},methods: {}
}).mount('#app')
Copy the code

This is the basic layout, the code is relatively large, you endure.

Move!

According to slide block

Let’s move the slider below to update vue data and set the required status data

  data() {
    return {
      showError: false.// Display an error message
      showSuccess: false.// A success message is displayed
      checking: false.// Display the check prompt
      sliding: false.// Whether the slider is currently being dragged
      slidMove: 0 // The distance the slider moved (px)
    };
  },
Copy the code

Three new functions have been added to Methods, which handle mousedown and lift events and a function to reset the slider

// Hold down the mouse
onMouseDown(event) {
  // When the user mouse down, the target is not a slider, no processing
  if(event.target.id ! = ="slider") {
    return;
  }
  if (this.checking) return;
  // Set the state to sliding
  this.sliding = true;
  // The following three variables do not need to be listened for, so do not put them in data
  
  // The clientX event property returns the horizontal coordinates of the mouse pointer relative to the browser page (or client area) when the event was triggered.
  // Record the x position when the mouse is pressed
  this.sliderLeft = event.clientX;
  this.sliderConWidth = this.$refs.sliderCon.clientWidth; // Record the width of the chute
  this.sliderWidth = this.$refs.slider.clientWidth; // Record the width of the slider
}
// The mouse is raised
onMouseUp(event) {
  if (this.sliding) {
    this.resetSlider()
  }
}
// Reset the slider
resetSlider() {
  this.sliding = false;
  this.slidMove = 0;
  this.checking = false;
  this.showSuccess = false;
  this.showError = false;
}
Copy the code

Bind events and data to the validation card.

To bind a mouse event to div.check:

<div class="check" @mousedown="onMouseDown" @mouseup="onMouseUp">

Dynamically bind class to #slider:

<div ref="slider" class="slider" id="slider" :class="{sliding}">
</div>
Copy the code

Add a CSS selector to the CSS

.slider-con .slider.sliding {
  background: #4ed3ff;
}
Copy the code

This will record the slider status as true when you press the slider, and the slider will turn blue. When you release the slider, the slider status will be false and the.sliding class will be lost, and the color will revert to white.

Now that we have the initial state, let’s focus on sliding.

To modify the CSS, go to the.slider -con.slider selector and add two CSS properties

.slider-con .slider {
  / *... Other attributes... * /
  
  --move: 0px;
  transform: translateX(var(--move));
}
Copy the code

When the mouse moves, all you need to do is change the CSS variable –move. Next, we can bind this variable to the div with id=”slider”. That’s the slider div.

<div ref="slider" class="slider" id="slider" :class="{sliding}" :style="{'--move': `${slidMove}px`}">
</div>
Copy the code

This dynamically binds the move CSS variable to the slidMove variable using :style

Add a new onMouseMove function to handle mouse movements.

// Handle mouse movement
onMouseMove(event) {
  if (this.sliding && this.checking === false) {
    // The slider to the right is equal to the X coordinate of the mouse movement event minus the original coordinate of the mouse press.
    let m = event.clientX - this.sliderLeft;
    if (m < 0) {
      // If m is less than 0, the user moves the mouse to the left beyond the initial position, which is 0
      // So it is directly equal to 0 to prevent crossing the boundary
      m = 0;
    } else if (m > this.sliderConWidth - this.sliderWidth) {
      // The maximum distance the slider moves to the right is the width of the chute minus the width of the slider.
      // Because the translateX function in CSS is calculated with the upper-left corner coordinates of the element
      // So subtract the width of the slider so that the upper left corner and the upper right corner of the chute do not overlap when the slider is on the far right.
      m = this.sliderConWidth - this.sliderWidth;
    }
    this.slidMove = m; }}Copy the code

Okay, so I can drag the slider.

Image rotation

Firstly, add two vUE calculation attributes, and calculate the rotation Angle according to the moving proportion of the slider moving relative to the movable space of the chute.

  computed: {
    angle() {
      let sliderConWidth = this.sliderConWidth ?? 0;
      let sliderWidth = this.sliderWidth ?? 0;
      let ratio = this.slidMove / (sliderConWidth - sliderWidth);
      // 360 degrees times the slider's movement ratio is the rotation Angle
      return 360 * ratio;
    },
    imgAngle() {
      return `rotate(The ${this.angle}deg)`; }}Copy the code

Then bind it to the DOM. Find the img element and modify it.

<img src="https://z3.ax1x.com/2021/08/06/fn7X4S.png" :style="{transform: imgAngle}" />
Copy the code

Verify that the rotation Angle is correct

Modify the HTML section to display various states.

<div class="img-con">
  <img src="https://z3.ax1x.com/2021/08/06/fn7X4S.png" :style="{transform: imgAngle}" />
  <div v-if="showError" class="check-state">error</div>
  <div v-else-if="showSuccess" class="check-state">correct</div>
  <div v-else-if="checking" class="check-state">In the validation</div>
</div>
Copy the code

Added the.check-state style

.check-state {
  width: 100%;
  height: 100%;
  background: rgba(0.0.0.0.5);
  color: white;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  justify-content:center;
  align-items:center;
}
Copy the code

Add a validApi function to methods to simulate the backend API to verify that the Angle is correct.

// Verify that the Angle is correct
validApi(angle) {
  return new Promise((resolve, reject) = > {
    // Simulate a network request
    setTimeout(() = > {
      // The Angle the image has been rotated
      const imgAngle = 90;
      // The sum of the rotation Angle of the image and the user's rotation Angle
      let sum = imgAngle + angle;
      // Error range
      const errorRang = 10;
      // When the sum of the user's rotation Angle and rotation Angle is 360 degrees, it means that the user has rotated a full circle
      // But you can't expect the user to get better by that much, so you need to leave some margin of error
      let isOk = Math.abs(360 - sum) <= errorRang;

      resolve(isOk);
    }, 1000);
  });
}
Copy the code

Next, modify the onMouseUp function to verify and handle the rotation Angle and state.

onMouseUp(event) {
  if (this.sliding && this.checking === false) {
    this.checking = true;
    this.validApi(this.angle)
      .then((isok) = > {
        if (isok) {
          this.showSuccess = true;
        } else {
          this.showError = true;
        }
        return new Promise((resolve, reject) = > {
          setTimeout(() = > {
            if (isok) {
              resolve(Math.round(this.angle));
            } else {
              reject();
            }
            this.resetSlider();
          }, 1000);
        });
      })
      .then((angle) = > {
        // Handle the business, or notify the caller that the validation was successful
        alert("Correct rotation:" + angle + "度");
      })
      .catche((e) = > {
        alert("Rotation error"); }); }}Copy the code

Let’s see what it looks like

Rich style

Now that the basics are in place, the slider is still not good enough, with only one press state, and now we can animate it with a shake when it makes a mistake.

First, add a CSS animation

@keyframes jitter {
  20% {
    transform: translateX(-5px);
  }
  40% {
    transform: translateX(10px);
  }
  60% {
    transform: translateX(-5px);
  }
  80% {
    transform: translateX(10px);
  }
  100% {
    transform: translateX(0); }}.slider-con.err-anim {
  animation: jitter 0.5 s;
}
/** The slider color under the error animation is red **/
.slider-con.err-anim .slider {
  background: #ff4e4e;
}
Copy the code

And then bind it to the chute.

<div ref="sliderCon" class="slider-con" :class="{'err-anim':showError}">.</div>
Copy the code

See the effect

The complete code

The code is written in Codepen, so you can go directly to Codepen to see the full code

SafeCheck (codepen.io)