A simple front-end implementation of a sliding puzzle captcha, with important parts annotated with comments. If you need to work with the back-end, you can refer to this approach. The back-end processes the image to generate a background image with a gap and a puzzle piece that fits the gap. The position of the puzzle piece is recorded in the SESSION
. The image and puzzle are then passed to the front-end for display. When the user drags and releases the mouse, the mouse trajectory and stopping position are sent to the back-end. The back-end retrieves the position information from the SESSION
and compares it with the position sent by the front-end. If necessary, the user's trajectory can be analyzed to distinguish between human and machine. If the position deviation is less than a certain threshold, the puzzle is considered solved.
<!DOCTYPE html>
<html>
<head>
<title>Sliding Puzzle Captcha</title>
<link rel="stylesheet" type="text/css" href="https://at.alicdn.com/t/font_1582902_u0zm91pv15i.css">
<style type="text/css">
.verify-slide-con{ /* Sliding puzzle container block */
width: 360px;
padding: 10px 20px;
border: 1px solid #eee;
}
.img-con{ /* Image container block */
width: 100%;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border: 1px solid #eee;
position: relative;
}
.img-con > .slide-block{ /* Slider in the image area */
top: 0;
left: 0;
position: absolute;
height: 40px;
width: 40px;
display: none;
background-repeat: no-repeat;
background-attachment: scroll;
background-size: 360px 200px;
z-index: 10;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.4), 0 0 10px 0 rgba(90, 90, 90, 0.4);
}
.img-con > .slide-block-mask{ /* Empty area in the image area */
top: 0;
left: 0;
position: absolute;
height: 40px;
width: 40px;
display: none;
background-color: rgba(0, 0, 0, 0.4);
}
.img-con > .img{ /* Image */
width: 100%;
height: 100%;
}
.img-con > .loading{ /* Loading style */
width: unset;
height: unset;
}
.slide-con{ /* Slider container */
height: 40px;
margin: 10px 0;
position: relative;
border: 1px solid #eee;
}
.slide-con > .slide-btn{ /* Slide button */
height: 40px;
width: 40px;
position: absolute;
background: #4C98F7;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.icon-arrow-right{ /* Right arrow */
font-size: 30px;
color: #fff;
}
.operate-con{ /* Operation container block */
border-top: 1px solid #eee;
height: 30px;
padding: 5px 0 0 5px;
display: flex;
align-items: center;
}
.icon-shuaxin1{ /* Refresh button */
color: #777;
font-size: 20px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="verify-slide-con"> <!-- Sliding puzzle container block -->
<div class="img-con"> <!-- Image container block -->
<img class="img"> <!-- Image -->
<div class="slide-block"></div> <!-- Puzzle -->
<div class="slide-block-mask"></div> <!-- Gap -->
</div>
<div class="slide-con"> <!-- Slider container -->
<div class="slide-btn"> <!-- Slide button -->
<i class="iconfont icon-arrow-right"></i> <!-- Icon -->
</div>
</div>
<div class="operate-con"> <!-- Operation container block -->
<i id="refresh" class="iconfont icon-shuaxin1"></i> <!-- Refresh button -->
</div>
</div>
</body>
<script type="text/javascript">
(function(){
var imgList = [ // Image group
"http://www.sdust.edu.cn/__local/9/7A/B1/F29B84DEF72DD329997E8172ABA_664BA3EF_32466.jpg",
"http://www.sdust.edu.cn/__local/B/F3/E4/693AB931C9FFB84646970D53BFE_C506394A_4282CA.jpg",
"http://www.sdust.edu.cn/__local/F/7A/AA/E1459849AA8AB0C89854A41BD41_BF3BD857_BC0D8.jpg",
"http://www.sdust.edu.cn/__local/1/95/CB/EDC1450B8FD1B8A25FAAC726AA4_A36D4253_16C91.jpg",
];
var imgCon = document.querySelector(".img-con"); // Image container element reference
var img = document.querySelector(".img-con > .img"); // Image element reference
var slideBlock = document.querySelector(".img-con > .slide-block"); // Slider element reference
var slideBlockMask = document.querySelector(".img-con > .slide-block-mask"); // Gap element reference
var slideCon = document.querySelector(".slide-con"); // Slide container reference
var slideBtn = document.querySelector(".slide-con > .slide-btn"); // Slide button reference
var refreshBtn = document.querySelector("#refresh"); // Refresh button reference
function randomInt(min=0, max=1) { // Generate random number
return min + ~~((max-min)*Math.random()) // min <= random < max
}
function initSlider(){
var maxTop = imgCon.offsetHeight -
~~(window.getComputedStyle(slideBlock).getPropertyValue("height").replace("px","")); // Get the maximum Y-axis offset
var maxRight = imgCon.offsetWidth -
~~(window.getComputedStyle(slideBlock).getPropertyValue("width").replace("px","")); // Get the maximum X-axis offset
var randPosY = randomInt(0, maxTop); // Random Y-axis offset
var randPosX = randomInt(60, maxRight); // Random X-axis offset
slideBtn.onmousedown = function(e){
slideBlock.style.display = "block"; // Display the puzzle
slideBlock.style.top=`${randPosY}px`; // Puzzle Y-axis offset
slideBlock.style["background-position"] = `-${randPosX}px -${randPosY}px`; // Specify the background image position
slideBlockMask.setAttribute("style", `display:block;top:${randPosY}px;left:${randPosX}px`); // Display the gap and specify the position
var edgeX = e.clientX; // Mouse click position
document.onmousemove = event => {
var relativeX = event.clientX - edgeX; // Mouse movement distance
if(relativeX<0 || relativeX>imgCon.offsetWidth-this.offsetWidth) return void 0; // Check if it exceeds the slide container block, do not move if it exceeds
slideBlock.style.left = relativeX + "px"; // Move the puzzle
this.style.left = relativeX + "px"; // Move the slide button
}
document.onmouseup = function() {
this.onmousemove = null; // Revoke event
this.onmouseup = null; // Revoke event
if(Math.abs(slideBlock.offsetLeft - slideBlockMask.offsetLeft)<=2) alert("Verification successful"); // If the offset distance is less than 2, it is considered successful
else alert("Verification failed"); // Otherwise, it failed
slideBlock.style.left = 0; // Puzzle reset
slideBtn.style.left = 0; // Slide button reset
};
}
}
function switchImg(){
slideBlock.style.display = "none"; // Do not display the puzzle
slideBlockMask.style.display = "none"; // Do not display the gap
img.classList.add("loading"); // Specify the loading style of the image
img.src="https://cdn.jsdelivr.net/gh/sentsin/layui@15d7241/dist/css/modules/layer/default/loading-2.gif"; // Loading animation
var newSrc = imgList[randomInt(0, 4)]; // Randomly load images
var tmp = new Image(); // Implicitly load images
tmp.src = newSrc; // Specify src
tmp.onload = function(){
img.classList.remove("loading"); // Revoke loading
img.src = newSrc; // Specify src, at this time the image is loaded from the cache
slideBlock.style["background-image"] = `url(${newSrc})`; // Puzzle background
initSlider(); // Initialize the slider
}
}
(function(){
switchImg(); // Load images
refreshBtn.addEventListener("click", e => switchImg()); // Bind event to refresh button
})();
})();
</script>
</html>
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/42082496
https://github.com/himushroom/verify-slide
https://www.cnblogs.com/xiaoshen666/p/10968750.html