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)