HTML+JavaScript滑动验证码实现
我将创建一个美观且功能完整的滑动验证码组件。这个实现包括滑块拖动、轨迹验证、成功反馈等完整功能。
思路分析
创建滑块轨道和可拖动的滑块
实现滑块拖动逻辑
添加验证逻辑(包括轨迹分析)
设计成功/失败状态反馈
添加重新验证功能
下面是完整实现代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滑动验证码</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px;
}
.container {
width: 100%;
max-width: 500px;
background-color: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
padding: 30px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #333;
font-size: 28px;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 16px;
}
.verification-area {
background-color: #f8f9fa;
border-radius: 10px;
padding: 25px;
margin-bottom: 25px;
border: 1px solid #eaeaea;
}
.verification-instruction {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px dashed #ddd;
}
.instruction-text {
font-size: 16px;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
.instruction-text i {
color: #4a6cf7;
}
.refresh-btn {
background-color: #f1f3f5;
border: none;
padding: 8px 15px;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #555;
transition: all 0.3s;
}
.refresh-btn:hover {
background-color: #e9ecef;
}
.slider-container {
position: relative;
height: 56px;
background-color: #e9ecef;
border-radius: 28px;
display: flex;
align-items: center;
padding: 0 3px;
margin-bottom: 15px;
overflow: hidden;
}
.slider-track {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: #e9ecef;
border-radius: 28px;
z-index: 1;
}
.slider-bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background: linear-gradient(90deg, #4a6cf7 0%, #6a8cff 100%);
border-radius: 28px;
opacity: 0.1;
z-index: 1;
}
.slider {
position: absolute;
left: 0;
top: 0;
height: 50px;
width: 50px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: grab;
box-shadow: 0 4px 12px rgba(74, 108, 247, 0.25);
z-index: 3;
transition: all 0.2s;
user-select: none;
}
.slider:active {
cursor: grabbing;
box-shadow: 0 6px 16px rgba(74, 108, 247, 0.35);
}
.slider i {
color: #4a6cf7;
font-size: 18px;
}
.slider-mask {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 50px;
background: linear-gradient(90deg, #4a6cf7 0%, #6a8cff 100%);
border-radius: 28px;
z-index: 2;
transition: width 0.1s;
}
.slider-text {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-weight: 500;
z-index: 2;
user-select: none;
}
.status-area {
display: flex;
align-items: center;
justify-content: center;
min-height: 60px;
margin-top: 20px;
padding: 15px;
border-radius: 8px;
background-color: #f8f9fa;
transition: all 0.3s;
}
.status-success {
background-color: rgba(40, 167, 69, 0.1);
color: #28a745;
}
.status-error {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.status-text {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 500;
}
.status-text i {
font-size: 20px;
}
.action-buttons {
display: flex;
gap: 15px;
margin-top: 20px;
}
.submit-btn, .reset-btn {
flex: 1;
padding: 15px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn {
background-color: #4a6cf7;
color: white;
}
.submit-btn:hover {
background-color: #3a5ce5;
}
.submit-btn:disabled {
background-color: #a0a0a0;
cursor: not-allowed;
}
.reset-btn {
background-color: #f8f9fa;
color: #333;
border: 1px solid #ddd;
}
.reset-btn:hover {
background-color: #e9ecef;
}
.hint {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: #777;
line-height: 1.5;
}
@media (max-width: 600px) {
.container {
padding: 20px;
}
.action-buttons {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-shield-alt"></i> 滑动验证</h1>
<p>请按住滑块,拖动到最右侧完成验证</p>
</div>
<div class="verification-area">
<div class="verification-instruction">
<div class="instruction-text">
<i class="fas fa-arrows-alt-h"></i>
<span>向右滑动完成验证</span>
</div>
<button class="refresh-btn" id="refreshBtn">
<i class="fas fa-redo"></i>
<span>换一个</span>
</button>
</div>
<div class="slider-container" id="sliderContainer">
<div class="slider-track"></div>
<div class="slider-bg"></div>
<div class="slider-mask" id="sliderMask"></div>
<div class="slider-text" id="sliderText">拖动滑块验证</div>
<div class="slider" id="slider">
<i class="fas fa-arrows-alt-h"></i>
</div>
</div>
<div class="status-area" id="statusArea">
<div class="status-text">
<i class="fas fa-info-circle"></i>
<span>等待验证...</span>
</div>
</div>
</div>
<div class="action-buttons">
<button class="submit-btn" id="submitBtn" disabled>提交验证</button>
<button class="reset-btn" id="resetBtn">重置验证</button>
</div>
<div class="hint">
<p><i class="fas fa-lightbulb"></i> 提示:请匀速拖动滑块,不要过快或过慢,以免验证失败</p>
</div>
</div>
<script>
// DOM元素
const slider = document.getElementById('slider');
const sliderContainer = document.getElementById('sliderContainer');
const sliderMask = document.getElementById('sliderMask');
const sliderText = document.getElementById('sliderText');
const statusArea = document.getElementById('statusArea');
const statusText = statusArea.querySelector('.status-text span');
const statusIcon = statusArea.querySelector('.status-text i');
const submitBtn = document.getElementById('submitBtn');
const resetBtn = document.getElementById('resetBtn');
const refreshBtn = document.getElementById('refreshBtn');
// 验证状态
let isVerified = false;
let isDragging = false;
let startX = 0;
let startTime = 0;
let endTime = 0;
let dragDistance = 0;
let dragPath = [];
let verificationPassed = false;
// 初始化
function init() {
resetVerification();
setupEventListeners();
}
// 设置事件监听器
function setupEventListeners() {
// 滑块鼠标/触摸事件
slider.addEventListener('mousedown', startDrag);
slider.addEventListener('touchstart', startDragTouch);
// 重置按钮
resetBtn.addEventListener('click', resetVerification);
// 刷新按钮
refreshBtn.addEventListener('click', refreshVerification);
// 提交按钮
submitBtn.addEventListener('click', submitVerification);
// 全局鼠标/触摸事件
document.addEventListener('mousemove', doDrag);
document.addEventListener('touchmove', doDragTouch);
document.addEventListener('mouseup', stopDrag);
document.addEventListener('touchend', stopDrag);
}
// 开始拖动(鼠标)
function startDrag(e) {
isDragging = true;
startX = e.clientX - slider.offsetLeft;
startTime = Date.now();
dragPath = [];
slider.style.cursor = 'grabbing';
updateStatus('正在验证中...', 'fas fa-sync-alt fa-spin', 'default');
}
// 开始拖动(触摸)
function startDragTouch(e) {
if (e.touches.length === 1) {
isDragging = true;
startX = e.touches[0].clientX - slider.offsetLeft;
startTime = Date.now();
dragPath = [];
slider.style.cursor = 'grabbing';
updateStatus('正在验证中...', 'fas fa-sync-alt fa-spin', 'default');
}
}
// 执行拖动(鼠标)
function doDrag(e) {
if (!isDragging) return;
e.preventDefault();
let x = e.clientX - startX;
handleDrag(x);
}
// 执行拖动(触摸)
function doDragTouch(e) {
if (!isDragging) return;
if (e.touches.length === 1) {
e.preventDefault();
let x = e.touches[0].clientX - startX;
handleDrag(x);
}
}
// 处理拖动逻辑
function handleDrag(x) {
const containerWidth = sliderContainer.offsetWidth;
const sliderWidth = slider.offsetWidth;
const maxX = containerWidth - sliderWidth;
// 限制滑块在容器内
if (x < 0) x = 0;
if (x > maxX) x = maxX;
// 更新滑块位置
slider.style.left = x + 'px';
sliderMask.style.width = (x + sliderWidth) + 'px';
// 记录拖动路径
dragPath.push({
x: x,
time: Date.now()
});
// 更新拖动距离
dragDistance = x;
// 检查是否到达最右侧
if (x >= maxX - 5) {
completeDrag();
}
}
// 完成拖动
function completeDrag() {
if (!isDragging) return;
isDragging = false;
endTime = Date.now();
// 验证拖动行为
verificationPassed = verifyDrag();
if (verificationPassed) {
// 验证成功
isVerified = true;
submitBtn.disabled = false;
updateStatus('验证成功!', 'fas fa-check-circle', 'success');
sliderText.textContent = '验证成功';
sliderText.style.color = '#28a745';
slider.style.background = 'linear-gradient(90deg, #28a745 0%, #5cd85c 100%)';
slider.innerHTML = '<i class="fas fa-check"></i>';
slider.style.cursor = 'default';
} else {
// 验证失败
updateStatus('验证失败,请重试', 'fas fa-times-circle', 'error');
sliderText.textContent = '验证失败';
sliderText.style.color = '#dc3545';
// 1秒后重置滑块
setTimeout(() => {
resetSlider();
updateStatus('验证失败,请重试', 'fas fa-times-circle', 'error');
}, 1000);
}
}
// 停止拖动
function stopDrag() {
if (!isDragging) return;
isDragging = false;
slider.style.cursor = 'grab';
// 如果没有到达最右侧,则滑块回弹
if (dragDistance < sliderContainer.offsetWidth - slider.offsetWidth - 5) {
resetSlider();
updateStatus('请拖动滑块到最右侧', 'fas fa-info-circle', 'default');
}
}
// 重置滑块位置
function resetSlider() {
slider.style.left = '0px';
sliderMask.style.width = '50px';
sliderText.textContent = '拖动滑块验证';
sliderText.style.color = '#666';
slider.style.background = 'white';
slider.innerHTML = '<i class="fas fa-arrows-alt-h"></i>';
slider.style.cursor = 'grab';
dragDistance = 0;
}
// 验证拖动行为
function verifyDrag() {
const dragTime = endTime - startTime;
const containerWidth = sliderContainer.offsetWidth;
const sliderWidth = slider.offsetWidth;
const maxX = containerWidth - sliderWidth;
// 检查是否到达最右侧
if (dragDistance < maxX - 5) {
return false;
}
// 检查拖动时间(不能太快也不能太慢)
if (dragTime < 500 || dragTime > 5000) {
return false;
}
// 检查拖动路径的平滑度(模拟真实拖动行为)
if (dragPath.length < 10) {
return false;
}
// 计算速度变化
let speedChanges = [];
for (let i = 1; i < dragPath.length; i++) {
const distance = dragPath[i].x - dragPath[i-1].x;
const time = dragPath[i].time - dragPath[i-1].time;
const speed = time > 0 ? distance / time : 0;
speedChanges.push(speed);
}
// 检查速度变化是否平滑(方差不能太大)
const avgSpeed = speedChanges.reduce((a, b) => a + b, 0) / speedChanges.length;
const variance = speedChanges.reduce((a, b) => a + Math.pow(b - avgSpeed, 2), 0) / speedChanges.length;
// 方差太大说明拖动不平滑,可能是机器人行为
if (variance > 0.05) {
return false;
}
return true;
}
// 更新状态显示
function updateStatus(text, icon, status) {
statusText.textContent = text;
statusIcon.className = icon;
// 移除所有状态类
statusArea.classList.remove('status-success', 'status-error');
// 添加新状态类
if (status === 'success') {
statusArea.classList.add('status-success');
} else if (status === 'error') {
statusArea.classList.add('status-error');
}
}
// 重置验证
function resetVerification() {
isVerified = false;
verificationPassed = false;
submitBtn.disabled = true;
resetSlider();
updateStatus('等待验证...', 'fas fa-info-circle', 'default');
}
// 刷新验证
function refreshVerification() {
resetVerification();
updateStatus('验证已刷新,请重新拖动', 'fas fa-sync-alt', 'default');
// 添加一个简单的动画效果
refreshBtn.querySelector('i').classList.add('fa-spin');
setTimeout(() => {
refreshBtn.querySelector('i').classList.remove('fa-spin');
}, 500);
}
// 提交验证
function submitVerification() {
if (!isVerified) {
updateStatus('请先完成滑块验证', 'fas fa-exclamation-circle', 'error');
return;
}
updateStatus('验证提交成功!', 'fas fa-check-circle', 'success');
// 在实际应用中,这里会向服务器发送验证结果
// 模拟API调用延迟
setTimeout(() => {
alert('验证成功!表单可以正常提交了。\n\n在实际应用中,这里会提交表单或继续下一步操作。');
}, 300);
}
// 初始化验证码
window.onload = init;
</script>
</body>
</html>
功能说明
滑块拖动验证:
- 用户可以拖动滑块到最右侧
- 滑块位置和背景会实时更新
- 提供视觉反馈
验证逻辑:
- 检查是否拖动到最右侧
- 验证拖动时间(不能太快或太慢)
- 分析拖动路径的平滑度
- 防止机器人行为(通过速度变化分析)
用户反馈:
- 实时显示验证状态
- 成功/失败的不同视觉样式
- 操作提示信息
额外功能:
这个实现模拟了真实滑动验证码的核心功能,包括防止机器人行为的简单检测。在实际应用中,后端还需要进行更复杂的验证,例如分析拖动轨迹、IP检测等。