如何使用HTML+JavaScript實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼
一、滑動(dòng)驗(yàn)證碼
在現(xiàn)代網(wǎng)絡(luò)安全體系中,人機(jī)驗(yàn)證機(jī)制扮演著至關(guān)重要的角色。傳統(tǒng)的文本驗(yàn)證碼由于識(shí)別困難、用戶體驗(yàn)差等問題逐漸被更先進(jìn)的驗(yàn)證方式取代。滑動(dòng)驗(yàn)證碼作為一種新型的人機(jī)驗(yàn)證手段,憑借其直觀的操作體驗(yàn)和良好的安全性,廣泛應(yīng)用于各類網(wǎng)站和應(yīng)用程序中。本文將詳細(xì)介紹如何使用 HTML、CSS 和 JavaScript 構(gòu)建一個(gè)完整的滑動(dòng)驗(yàn)證碼系統(tǒng)。
二、效果演示
滑動(dòng)驗(yàn)證碼的核心交互流程包括圖像加載、拼圖生成、用戶拖拽和驗(yàn)證判斷四個(gè)階段,用戶通過拖拽右側(cè)滑塊向右移動(dòng),使拼圖塊與背景圖像中的缺口對(duì)齊,驗(yàn)證成功時(shí)顯示綠色成功提示,失敗則顯示紅色錯(cuò)誤信息并自動(dòng)重置。



三、系統(tǒng)分析
1、頁面結(jié)構(gòu)
整個(gè)滑動(dòng)驗(yàn)證碼系統(tǒng)采用簡潔清晰的 HTML 結(jié)構(gòu)設(shè)計(jì),主要包括圖像顯示區(qū)(verify-img)、滑動(dòng)控制區(qū)(erify-bar-box)和結(jié)果顯示區(qū)(verify-result)三個(gè)核心部分。
<div class="verify-container">
<div class="verify-box">
<div class="verify-img">
<img class="back-img" src="" style="width:100%;height:100%;"/>
<div class="loading-indicator" id="backImgLoading" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#666;font-size:14px;display:none;">加載中...</div>
</div>
<div class="verify-bar-box">
<span class="verify-msg">向右滑動(dòng)完成驗(yàn)證</span>
<div class="verify-left-bar"></div>
<div class="verify-move-block">
<span>></span>
<div class="verify-sub-block">
<img class="block-img" src="" style="width:100%;height:100%;"/>
</div>
</div>
</div>
</div>
<div class="verify-result" id="verifyResult"></div>
</div>
2、核心功能實(shí)現(xiàn)
2.1 初始化流程
主要實(shí)現(xiàn)了隨機(jī)位置生成、圖像加載和拼圖繪制三個(gè)步驟。
async function init() {
// 顯示加載指示器
showLoading()
targetX = Math.floor(Math.random() * (imgWidth - bolckSize - 60)) + 30;
targetY = Math.floor(Math.random() * (imgHeight - bolckSize - 20)) + 10;
var img = await loadImg(imgUrl+'?'+Math.random());
// 創(chuàng)建背景畫布并繪制帶缺口的圖像
var backCanvas = document.createElement('canvas')
backCanvas.width = imgWidth;
backCanvas.height = imgHeight;
var backCtx = backCanvas.getContext('2d');
backCtx.drawImage(img, 0, 0, 380, 190, 0, 0, imgWidth, imgHeight);
backCtx.fillStyle = '#FFFFFF';
backCtx.fillRect(targetX, targetY, 50, 50);
backImg.src = backCanvas.toDataURL('image/png');
// 創(chuàng)建拼圖塊
var canvas = document.createElement('canvas')
canvas.width = 50;
canvas.height = 50;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, targetX, targetY, bolckSize, bolckSize, 0, 0, bolckSize, bolckSize)
blockImg.src = canvas.toDataURL('image/png');
subBlock.style.top = (-201 + targetY) + 'px';
// 隱藏加載指示器
hideLoading()
}
2.2 拖拽交互處理
通過鼠標(biāo)事件監(jiān)聽實(shí)現(xiàn)流暢的拖拽體驗(yàn),當(dāng)用戶在滑塊上按下鼠標(biāo)并移動(dòng)時(shí),滑塊滑塊會(huì)隨鼠標(biāo)移動(dòng);當(dāng)用戶釋放鼠標(biāo)時(shí),進(jìn)行位置校驗(yàn),如果失敗滑塊會(huì)變?yōu)榧t色并平滑的回到起點(diǎn)位置。
// 鼠標(biāo)按下事件 - 開始拖拽
moveBlock.addEventListener('mousedown', function(e) {
isDragging = true;
startX = e.clientX;
moveBlock.style.backgroundColor = '#337AB7';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #337AB7';
});
// 鼠標(biāo)移動(dòng)事件 - 拖拽過程
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
var newLeft = e.clientX - startX - 2;
// 限制滑塊移動(dòng)范圍
if (newLeft < 0) newLeft = 0;
if (newLeft > maxWidth) newLeft = maxWidth;
moveBlock.style.left = newLeft + 'px';
verifyLeftBar.style.width = newLeft + 'px';
verifyLeftBar.style.border = '1px solid #337AB7';
});
// 鼠標(biāo)釋放事件 - 結(jié)束拖拽
document.addEventListener('mouseup', function() {
if (!isDragging) return;
isDragging = false;
var currentPosition = moveBlock.offsetLeft;
if (Math.abs(currentPosition - targetX) <= tolerance) {
moveBlock.style.backgroundColor = '#5CB85C';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #5CB85C';
// 顯示成功提示
verifyResult.textContent = '驗(yàn)證成功!';
verifyResult.className = 'verify-result success';
return;
}
moveBlock.style.backgroundColor = '#D9534F';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #D9534F';
verifyLeftBar.style.backgroundColor = '#fff0f0';
// 顯示失敗提示
verifyResult.textContent = '驗(yàn)證失敗,請(qǐng)重試';
verifyResult.className = 'verify-result fail';
// 滑塊回彈動(dòng)畫
moveBlock.style.transition = 'left 0.8s';
moveBlock.style.left = '0px';
verifyLeftBar.style.transition = 'width 0.8s';
verifyLeftBar.style.width = '0px';
// 動(dòng)畫結(jié)束后清除過渡效果
setTimeout(() => {
init()
moveBlock.style.transition = '';
verifyLeftBar.style.transition = '';
moveBlock.style.backgroundColor = '#FFFFFF';
moveBlock.style.color = '#999';
verifyLeftBar.style.backgroundColor = '#F0FFF0';
// 清除驗(yàn)證結(jié)果提示
verifyResult.className = 'verify-result';
}, 800);
});
四、擴(kuò)展建議
- 添加服務(wù)器端驗(yàn)證,防止客戶端偽造結(jié)果
- 增加行為特征檢測(cè),識(shí)別自動(dòng)化工具攻擊
- 添加個(gè)性化拼圖形狀設(shè)計(jì),豐富視覺表現(xiàn)
- 增加重試次數(shù)限制機(jī)制,防止無限重試
五、完整代碼
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滑動(dòng)驗(yàn)證碼</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #f5f5f5;
}
.container {
display: flex;
padding: 20px;
justify-content: center;
}
.verify-container {
background: #fff;
width:400px;
padding: 10px;
border: 1px solid #ddd;
user-select: none;
}
.verify-img {
width: 380px;
height: 190px;
margin-bottom: 10px;
position: relative;
}
.verify-bar-box {
width: 380px;
height: 50px;
line-height: 50px;
position: relative;
background: #FFFFFF;
text-align: center;
box-sizing: content-box;
border: 1px solid #ddd;
border-radius: 4px;
color: #999;
}
.verify-left-bar {
background: #f0fff0;
position: absolute;
top: 0;
left: 0;
height: 50px;
}
.verify-move-block {
position: absolute;
top: 0;
left: 0;
background: #fff;
cursor: pointer;
box-sizing: content-box;
box-shadow: 0 0 2px #888888;
border-radius: 1px;
width: 50px;
height: 50px;
}
.verify-sub-block {
position: absolute;
border: 1px solid #ddd;
height: 50px;
left: -2px;
top: -201px;
}
.verify-result {
margin-top: 10px;
padding: 8px 12px;
text-align: center;
border-radius: 4px;
font-weight: bold;
display: none;
}
.verify-result.success {
background-color: #dff0d8;
color: #3c763d;
border: 1px solid #d6e9c6;
display: block;
}
.verify-result.fail {
background-color: #f2dede;
color: #a94442;
border: 1px solid #ebccd1;
display: block;
}
.loading-indicator {
padding: 5px 10px;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="container">
<div class="verify-container">
<div class="verify-box">
<div class="verify-img">
<img class="back-img" src="" style="width:100%;height:100%;"/>
<div class="loading-indicator" id="backImgLoading" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#666;font-size:14px;display:none;">加載中...</div>
</div>
<div class="verify-bar-box">
<span class="verify-msg">向右滑動(dòng)完成驗(yàn)證</span>
<div class="verify-left-bar"></div>
<div class="verify-move-block">
<span>></span>
<div class="verify-sub-block">
<img class="block-img" src="" style="width:100%;height:100%;"/>
</div>
</div>
</div>
</div>
<div class="verify-result" id="verifyResult"></div>
</div>
</div>
<script>
var verifyBarBox = document.querySelector('.verify-bar-box');
var moveBlock = document.querySelector('.verify-move-block');
var verifyLeftBar = document.querySelector('.verify-left-bar');
var backImg = document.querySelector('.back-img');
var subBlock = document.querySelector('.verify-sub-block');
var blockImg = document.querySelector('.block-img');
var verifyResult = document.getElementById('verifyResult');
var backImgLoading = document.getElementById('backImgLoading');
var startX = 0;
var isDragging = false;
var maxWidth = verifyBarBox.offsetWidth - moveBlock.offsetWidth;
var imgUrl = 'https://picsum.photos/380/190';
var imgWidth = 380;
var imgHeight = 190;
var bolckSize = 50;
var targetX = 0;
var targetY = 0;
var tolerance = 5;
init();
async function init() {
// 顯示加載指示器
showLoading()
targetX = Math.floor(Math.random() * (imgWidth - bolckSize - 60)) + 30;
targetY = Math.floor(Math.random() * (imgHeight - bolckSize - 20)) + 10;
var img = await loadImg(imgUrl+'?'+Math.random());
// 創(chuàng)建背景畫布并繪制帶缺口的圖像
var backCanvas = document.createElement('canvas')
backCanvas.width = imgWidth;
backCanvas.height = imgHeight;
var backCtx = backCanvas.getContext('2d');
backCtx.drawImage(img, 0, 0, 380, 190, 0, 0, imgWidth, imgHeight);
backCtx.fillStyle = '#FFFFFF';
backCtx.fillRect(targetX, targetY, 50, 50);
backImg.src = backCanvas.toDataURL('image/png');
// 創(chuàng)建拼圖塊
var canvas = document.createElement('canvas')
canvas.width = 50;
canvas.height = 50;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, targetX, targetY, bolckSize, bolckSize, 0, 0, bolckSize, bolckSize)
blockImg.src = canvas.toDataURL('image/png');
subBlock.style.top = (-201 + targetY) + 'px';
// 隱藏加載指示器
hideLoading()
}
function showLoading() {
backImgLoading.style.display = 'block';
subBlock.style.display = 'none'
backImg.style.display = 'none'
}
function hideLoading() {
backImgLoading.style.display = 'none';
subBlock.style.display = 'block'
backImg.style.display = 'block'
}
// 繪制拼圖塊
function loadImg(url){
return new Promise((res,rej)=>{
const im = new Image();
im.crossOrigin='anonymous';
im.onload = ()=>res(im);
im.onerror= rej;
im.src = url;
});
}
// 鼠標(biāo)按下事件 - 開始拖拽
moveBlock.addEventListener('mousedown', function(e) {
isDragging = true;
startX = e.clientX;
moveBlock.style.backgroundColor = '#337AB7';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #337AB7';
});
// 鼠標(biāo)移動(dòng)事件 - 拖拽過程
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
var newLeft = e.clientX - startX - 2;
// 限制滑塊移動(dòng)范圍
if (newLeft < 0) newLeft = 0;
if (newLeft > maxWidth) newLeft = maxWidth;
moveBlock.style.left = newLeft + 'px';
verifyLeftBar.style.width = newLeft + 'px';
verifyLeftBar.style.border = '1px solid #337AB7';
});
// 鼠標(biāo)釋放事件 - 結(jié)束拖拽
document.addEventListener('mouseup', function() {
if (!isDragging) return;
isDragging = false;
var currentPosition = moveBlock.offsetLeft;
if (Math.abs(currentPosition - targetX) <= tolerance) {
moveBlock.style.backgroundColor = '#5CB85C';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #5CB85C';
// 顯示成功提示
verifyResult.textContent = '驗(yàn)證成功!';
verifyResult.className = 'verify-result success';
return;
}
moveBlock.style.backgroundColor = '#D9534F';
moveBlock.style.color = '#FFFFFF';
verifyLeftBar.style.border = '1px solid #D9534F';
verifyLeftBar.style.backgroundColor = '#fff0f0';
// 顯示失敗提示
verifyResult.textContent = '驗(yàn)證失敗,請(qǐng)重試';
verifyResult.className = 'verify-result fail';
// 滑塊回彈動(dòng)畫
moveBlock.style.transition = 'left 0.8s';
moveBlock.style.left = '0px';
verifyLeftBar.style.transition = 'width 0.8s';
verifyLeftBar.style.width = '0px';
// 動(dòng)畫結(jié)束后清除過渡效果
setTimeout(() => {
init()
moveBlock.style.transition = '';
verifyLeftBar.style.transition = '';
moveBlock.style.backgroundColor = '#FFFFFF';
moveBlock.style.color = '#999';
verifyLeftBar.style.backgroundColor = '#F0FFF0';
// 清除驗(yàn)證結(jié)果提示
verifyResult.className = 'verify-result';
}, 800);
});
</script>
</body>
</html>
總結(jié)
到此這篇關(guān)于如何使用HTML+JavaScript實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼的文章就介紹到這了,更多相關(guān)HTML+JS滑動(dòng)驗(yàn)證碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript常用數(shù)學(xué)函數(shù)用法示例
這篇文章主要介紹了JavaScript常用數(shù)學(xué)函數(shù)用法,結(jié)合實(shí)例形式分析了JavaScript常見的對(duì)數(shù)、平方、絕對(duì)值、正弦、四舍五入等相關(guān)數(shù)學(xué)函數(shù)使用技巧,需要的朋友可以參考下2018-05-05
JavaScript中object和Object的區(qū)別(詳解)
下面小編就為大家?guī)硪黄狫avaScript中object和Object的區(qū)別(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
動(dòng)態(tài)的9*9乘法表效果的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄獎(jiǎng)討B(tài)的9*9乘法表效果的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考,一起跟隨小編過來看看吧2016-05-05
JavaScript動(dòng)態(tài)修改網(wǎng)頁元素內(nèi)容的方法
這篇文章主要介紹了JavaScript動(dòng)態(tài)修改網(wǎng)頁元素內(nèi)容的方法,實(shí)例分析了javascript操作html元素的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03

