前言
web端验证码输入框常用于用户注册、登录、支付确认场景。与传统的单输入框验证码相比,多输入框分离能够有效防止自动填充和机器人攻击,同时提供更直观的用户体验。
本文将详细介绍如何使用纯前端技术实现一个验证码输入框组件,包含:
- 支持自动验证、回车处理
- 支持一次性粘贴6位验证码
- 输入完成后自动提交验证
- 成功/失败提示和动画效果
1. 实现思路和效果
1.1 实现思路
1. 初始化阶段
flowchart TD
A[加载DOM元素] --> B[设置初始状态]
B --> C[绑定事件监听器]
C --> D[禁用所有输入框]
- 获取所有输入框DOM元素
- 初始化状态变量(isActive、countdown等)
- 绑定所有必要的事件监听器
- 设置输入框为禁用状态
2. 验证码发送流程
flowchart TD
A[点击发送按钮] --> B[启动60秒倒计时]
B --> C[激活输入框]
C --> D[自动聚焦首个输入框]
D --> E[模拟发送验证码]
- 按钮点击触发倒计时
- 激活所有输入框的交互状态
- 自动聚焦到第一个输入框
- 显示模拟的验证码发送提示
- 输入处理流程
flowchart TD
A[输入/粘贴事件] --> B{验证输入内容}
B -->|有效数字| C[自动跳转下一输入框]
B -->|无效输入| D[清空当前输入]
C --> E{是否填满6位}
E -->|是| F[触发自动验证]
E -->|否| G[等待继续输入]
- 实时验证输入内容是否为数字
- 自动处理输入框之间的跳转逻辑
- 支持退格键回退功能
- 完整输入后自动触发验证
4. 验证处理流程
flowchart TD
A[收集输入内容] --> B{验证逻辑}
B -->|验证成功| C[显示成功状态]
B -->|验证失败| D[显示错误状态]
C --> E[准备后续操作]
D --> F[保留输入框激活状态]
F --> G[允许重新输入]
- 验证过程
- 根据结果显示不同的UI状态
- 成功时执行跳转逻辑
- 失败时保留输入状态允许重试
1.2 实现效果
- 成功效果

- 失败效果

2. 代码实现
2.1 HTML结构
<div class="otp-container">
<h2>请输入6位验证码</h2>
<div class="otp-field">
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
</div>
<div class="message error-message"></div>
<div class="message success-message"></div>
<button class="resend-btn">发送验证码</button>
</div>
关键点:
- 使用readonly
属性初始禁用输入框
- inputmode="numeric"
确保移动设备显示数字键盘
- pattern="[0-9]*"
限制只允许数字输入
2.2 CSS样式
.otp-field input {
width: 40px;
height: 40px;
text-align: center;
border: 1px solid #ddd;
border-radius: 5px;
margin-right: 10px;
font-size: 18px;
transition: all 0.3s ease;
background-color: #f9f9f9;
color: #999;
}
.otp-field input.active {
background-color: white;
color: #333;
}
.otp-field input:focus {
border-color: #4CAF50;
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
transform: scale(1.05);
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-5px); }
40%, 80% { transform: translateX(5px); }
}
.otp-field input.error {
border-color: #f44336;
animation: shake 0.5s;
}
关键点:
- 使用CSS过渡实现平滑的聚焦效果
- 抖动动画增强错误反馈
- 通过.active
类控制输入框视觉状态
2.3 js核心逻辑
2.3.1 初始化阶段变量声明
const inputs = document.querySelectorAll('.otp-field input');
const errorMessage = document.querySelector('.error-message');
const successMessage = document.querySelector('.success-message');
const resendBtn = document.querySelector('.resend-btn');
let otpCode = '';
let countdown = 60;
let countdownInterval;
let isActive = false;
-
inputs:获取所有验证码输入框元素
-
errorMessage/successMessage:获取错误和成功提示元素
-
resendBtn:获取发送验证码按钮
-
otpCode:存储当前输入的验证码
-
countdown:倒计时秒数(初始60秒)
-
countdownInterval:存储倒计时定时器
-
isActive:控制输入框是否可用的标志
2.3.2 初始化函数
function initInputs() {
// 粘贴事件监听
document.addEventListener('paste', handlePaste);
// 为每个输入框添加事件
inputs.forEach((input, index) => {
input.addEventListener('input', handleInput.bind(null, index));
input.addEventListener('keydown', handleKeydown.bind(null, index));
});
}
// 初始调用
initInputs();
deactivateInputs(); // 初始禁用输入框
- 在document级别监听粘贴事件
- 为每个输入框绑定input和keydown事件
- 初始状态下禁用所有输入框
2.3.3 输入框状态管理
- 激活输入框:
function activateInputs() {
isActive = true;
inputs.forEach(input => {
input.readOnly = false;
input.classList.add('active');
});
inputs[0].focus(); // 自动聚焦第一个输入框
}
- 设置
isActive
为true
- 移除所有输入框的
readOnly
属性
- 添加
active
类改变状态
- 自动聚焦到第一个输入框
- 禁用输入框:
function deactivateInputs() {
isActive = false;
inputs.forEach(input => {
input.readOnly = true;
input.classList.remove('active');
input.value = ''; // 清空所有输入
});
}
- 设置
isActive
为false
- 添加
readOnly
属性禁用输入
- 移除
active
类恢复默认样式
- 清空所有输入框内容
2.3.4 事件处理逻辑
- 粘贴事件处理
function handlePaste(e) {
if (!isActive) return; // 检查是否激活状态
// 检查是否粘贴到验证码输入框中
const activeElement = document.activeElement;
if (!Array.from(inputs).includes(activeElement)) return;
e.preventDefault();
const pasteData = e.clipboardData.getData('text/plain').trim();
// 验证是否为6位数字
if (/^\d{6}$/.test(pasteData)) {
// 填充到各个输入框
pasteData.split('').forEach((char, i) => {
if (inputs[i]) inputs[i].value = char;
});
inputs[5].focus(); // 聚焦最后一个
checkCompletion(); // 检查是否完成
} else {
showError("请粘贴6位数字验证码");
shakeInputs(); // 错误抖动效果
}
}
2. 输入事件处理
function handleInput(index) {
if (!isActive) return;
const value = this.value;
// 只允许数字输入
if (!/^\d*$/.test(value)) {
this.value = '';
return;
}
if (value.length === 1) {
// 自动跳转下一个输入框
const nextIndex = index + 1;
if (nextIndex < inputs.length) inputs[nextIndex].focus();
checkCompletion(); // 检查是否完成
}
}
3. 键盘事件处理
function handleKeydown(index, e) {
if (!isActive) return;
// 处理退格键
if (e.key === 'Backspace' && this.value.length === 0) {
const prevIndex = index - 1;
if (prevIndex >= 0) {
inputs[prevIndex].focus();
inputs[prevIndex].select(); // 选中上一个输入框内容
}
}
}
2.3.5 验证逻辑
- 检查输入完成
function checkCompletion() {
otpCode = Array.from(inputs).map(input => input.value).join('');
if (otpCode.length === 6) {
// 延迟300ms后验证
setTimeout(() => verifyOtp(otpCode), 300);
}
}
2. 验证处理
function verifyOtp(code) {
// 模拟验证(实际项目替换为API调用)
const isValid = code === '123456';
if (isValid) {
showSuccess("验证成功!正在跳转...");
setTimeout(() => {
alert("验证成功!");
resetForm();
deactivateInputs();
}, 1500);
} else {
showError("验证码错误,请重新输入");
shakeInputs();
}
}
2.3.6 反馈效果
- 成功失败消息提示
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
errorMessage.classList.add('fade-in');
successMessage.style.display = 'none';
}
function showSuccess(message) {
successMessage.textContent = message;
successMessage.style.display = 'block';
successMessage.classList.add('fade-in');
errorMessage.style.display = 'none';
}
2. 错误抖动效果
function shakeInputs() {
inputs.forEach(input => input.classList.add('error'));
setTimeout(() => {
inputs.forEach(input => input.classList.remove('error'));
}, 500);
}
2.3.7 验证码发送逻辑
- 倒计时管理
function initResendButton() {
updateResendButton();
countdownInterval = setInterval(() => {
countdown--;
updateResendButton();
if (countdown <= 0) clearInterval(countdownInterval);
}, 1000);
}
function updateResendButton() {
resendBtn.textContent = countdown > 0
? `${countdown}秒后重新发送`
: "重新发送验证码";
resendBtn.disabled = countdown > 0;
}
2. 发送验证码
resendBtn.addEventListener('click', function() {
alert("验证码已发送到您的手机/邮箱");
countdown = 60; // 重置倒计时
initResendButton();
activateInputs(); // 激活输入框
});
3. 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修复粘贴功能的验证码输入框</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.otp-container {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 100%;
}
h2 {
color: #333;
font-weight: normal;
font-size: 18px;
margin-bottom: 20px;
}
.otp-field {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.otp-field input {
width: 40px;
height: 40px;
text-align: center;
border: 1px solid #ddd;
border-radius: 5px;
margin-right: 10px;
font-size: 18px;
transition: all 0.3s ease;
background-color: #f9f9f9;
color: #999;
}
.otp-field input.active {
background-color: white;
color: #333;
}
.otp-field input:focus {
border-color: #4CAF50;
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
outline: none;
transform: scale(1.05);
}
.otp-field input.error {
border-color: #f44336;
animation: shake 0.5s;
}
.message {
margin-top: 15px;
padding: 10px;
border-radius: 5px;
display: none;
}
.error-message {
background-color: #ffebee;
color: #f44336;
}
.success-message {
background-color: #e8f5e9;
color: #4CAF50;
}
.resend-btn {
margin-top: 20px;
padding: 8px 16px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.resend-btn:hover {
background-color: #0b7dda;
}
.resend-btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-5px); }
40%, 80% { transform: translateX(5px); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
</style>
</head>
<body>
<div class="otp-container">
<h2>请输入6位验证码</h2>
<div class="otp-field">
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
<input type="text" maxlength="1" pattern="[0-9]*" inputmode="numeric" readonly />
</div>
<div class="message error-message"></div>
<div class="message success-message"></div>
<button class="resend-btn">发送验证码</button>
</div>
<script>
const inputs = document.querySelectorAll('.otp-field input');
const errorMessage = document.querySelector('.error-message');
const successMessage = document.querySelector('.success-message');
const resendBtn = document.querySelector('.resend-btn');
let otpCode = '';
let countdown = 60;
let countdownInterval;
let isActive = false;
// 初始化输入框事件
function initInputs() {
// 在document上监听粘贴事件
document.addEventListener('paste', function(e) {
if (!isActive) return;
// 检查是否粘贴到验证码输入框中
const activeElement = document.activeElement;
if (!Array.from(inputs).includes(activeElement)) return;
e.preventDefault();
const pasteData = e.clipboardData.getData('text/plain').trim();
// 验证粘贴内容是否为6位数字
if (/^\d{6}$/.test(pasteData)) {
// 将粘贴内容分割并填充到各个输入框
pasteData.split('').forEach((char, i) => {
if (inputs[i]) {
inputs[i].value = char;
}
});
// 自动聚焦最后一个输入框
inputs[5].focus();
// 检查是否完成输入
checkCompletion();
} else {
// 粘贴内容不符合要求,显示错误
showError("请粘贴6位数字验证码");
shakeInputs();
}
});
inputs.forEach((input, index) => {
// 输入事件处理
input.addEventListener('input', function() {
if (!isActive) return;
const value = this.value;
// 只允许数字输入
if (!/^\d*$/.test(value)) {
this.value = '';
return;
}
if (value.length === 1) {
// 自动跳转下一个输入框
const nextIndex = index + 1;
if (nextIndex < inputs.length) {
inputs[nextIndex].focus();
}
// 检查是否所有输入框都已填写
checkCompletion();
}
});
// 键盘事件处理
input.addEventListener('keydown', function(e) {
if (!isActive) return;
if (e.key === 'Backspace') {
const value = this.value;
if (value.length === 0) {
// 退格键处理空输入框
const prevIndex = index - 1;
if (prevIndex >= 0) {
inputs[prevIndex].focus();
inputs[prevIndex].select();
}
}
}
});
});
}
// 激活输入框
function activateInputs() {
isActive = true;
inputs.forEach(input => {
input.readOnly = false;
input.classList.add('active');
});
inputs[0].focus();
}
// 禁用输入框
function deactivateInputs() {
isActive = false;
inputs.forEach(input => {
input.readOnly = true;
input.classList.remove('active');
input.value = '';
});
}
// 检查是否所有输入框都已填写
function checkCompletion() {
otpCode = Array.from(inputs).map(input => input.value).join('');
if (otpCode.length === 6) {
// 所有输入框都已填写,触发自动提交
setTimeout(() => {
verifyOtp(otpCode);
}, 300);
}
}
// 验证验证码
function verifyOtp(code) {
// 这里应该是实际的验证逻辑,这里用模拟验证
const isValid = code === '123456'; // 固定验证码123456
if (isValid) {
// 验证成功
showSuccess("验证成功!正在跳转...");
// 模拟跳转
setTimeout(() => {
alert("验证成功!在实际应用中这里会进行页面跳转或执行其他操作。");
resetForm();
deactivateInputs();
}, 1500);
} else {
// 验证失败
showError("验证码错误,请重新输入");
shakeInputs();
}
}
// 显示错误信息
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
errorMessage.classList.add('fade-in');
successMessage.style.display = 'none';
}
// 显示成功信息
function showSuccess(message) {
successMessage.textContent = message;
successMessage.style.display = 'block';
successMessage.classList.add('fade-in');
errorMessage.style.display = 'none';
}
// 重置表单
function resetForm() {
inputs.forEach(input => {
input.classList.remove('error');
});
errorMessage.style.display = 'none';
successMessage.style.display = 'none';
}
// 输入框抖动效果
function shakeInputs() {
inputs.forEach(input => {
input.classList.add('error');
});
setTimeout(() => {
inputs.forEach(input => {
input.classList.remove('error');
});
}, 500);
}
// 初始化重新发送按钮倒计时
function initResendButton() {
updateResendButton();
countdownInterval = setInterval(() => {
countdown--;
updateResendButton();
if (countdown <= 0) {
clearInterval(countdownInterval);
resendBtn.textContent = "重新发送验证码";
resendBtn.disabled = false;
}
}, 1000);
}
// 更新重新发送按钮状态
function updateResendButton() {
if (countdown > 0) {
resendBtn.textContent = `${countdown}秒后重新发送`;
resendBtn.disabled = true;
}
}
// 重新发送验证码
resendBtn.addEventListener('click', function() {
// 模拟发送验证码
alert("验证码已发送到您的手机/邮箱");
// 重置倒计时
countdown = 60;
initResendButton();
// 激活输入框
activateInputs();
});
// 初始化
initInputs();
deactivateInputs(); // 初始状态下禁用输入框
</script>
</body>
</html>
4. 总结
最后总结一下,前端验证码输入框核心逻辑就是监听input输入,进行聚焦输入框,并检查是否全部输入;监听回车,可进行删除,聚焦前一个。
如有错误,请指正O^O!