本文详解为何otp输入框的focus/blur逻辑在change事件中失效,并提供基于input或keyup事件的可靠解决方案,确保用户输入单个数字后立即自动跳转至下一个输入框。
在构建六位OTP(One-Time Password)输入组件时,一个常见需求是:用户在某个输入框中输入一位数字后,焦点自动移至下一个输入框。许多开发者会自然选择 change 事件来监听值的变化并触发 focus(),但实际运行中却常发现跳转失败——控制台日志显示 blur 和 focus 被调用,但光标并未真正落到下一个输入框上。
根本原因在于 change 事件的触发时机:它仅在输入框失去焦点(blur)且值发生变更后才触发,而非实时响应输入。这意味着当用户刚键入一个数字(如“5”),此时输入框仍处于聚焦状态,change 事件尚未触发;只有当用户手动点击其他区域、按 Tab 键或主动失焦后,change 才执行——这完全违背了“即时跳转”的交互预期。
✅ 正确做法是改用 input 事件(推荐)或 keyup 事件:
以下是优化后的核心代码(已修复ID重复、maxlength拼写、焦点竞争等问题):
const OTP_LENGTH = 6;
const textInputs = [];
const otpContainer = document.getElementById('otp-container');
for (let i = 0; i < OTP_LENGTH; i++) {
const input = document.createElement('input');
input.type = 'text'; // 使用 text 更稳妥(number 类型在部分设备上会触发软键盘数字模式但限制多)
input.maxLength = 1; // ✅ 正确属性名:maxLength(非 max-length)
input.id = `otp-input-${i}`; // ✅ 避免重复 ID(原代码所有 input id='otp-input')
input.dataset.otpPos = i;
input.className = 'otp-input';
input.addEventListener('input', (e) => {
const target = e.target;
const position = parseInt(target.dataset.otpPos, 10);
// 清空非法字符(如字母、符号),只保留数字
target.value = target.value.replace(/[^0-
9]/g, '');
if (target.value && position < OTP_LENGTH - 1) {
// 延迟聚焦以避免浏览器焦点调度冲突(关键!)
setTimeout(() => {
const nextInput = textInputs[position + 1];
if (nextInput) {
nextInput.focus();
}
}, 0);
}
});
input.addEventListener('keydown', (e) => {
// 支持 Backspace 删除时反向跳转(可选增强)
if (e.key === 'Backspace' && !e.target.value && e.target.dataset.otpPos > 0) {
setTimeout(() => {
const prevInput = textInputs[parseInt(e.target.dataset.otpPos, 10) - 1];
if (prevInput) prevInput.focus();
}, 0);
}
});
textInputs.push(input);
otpContainer.appendChild(input);
}
// 自动聚焦第一个输入框
if (textInputs[0]) textInputs[0].focus();⚠️ 关键注意事项:
总结:OTP自动跳转的核心在于选择实时响应的事件源(input > keyup ≫ change),配合异步聚焦与输入校验,即可实现流畅、健壮、跨端一致的用户体验。