
本文讲解为何otp输入框的change事件无法触发自动聚焦,以及如何改用input或keyup事件来正确实现数字输入后自动跳转到下一个输入框的功能。
在构建6位一次性密码(OTP)输入组件时,一个常见需求是:用户在某个输入框中输入一位数字后,焦点自动移至下一个输入框。然而,若使用 change 事件监听值变化,会发现该逻辑几乎从不生效——因为 change 事件仅在输入框失去焦点(blur)且内容发生过修改时才触发,而非实时响应每一次输入。
这正是原代码失效的根本原因:
input.onchange = (e) => { /* ... */ }; // ❌ 错误时机:用户尚未离开当前输入框,change 不会触发
✅ 正确做法是监听 input 事件(推荐)或 keyup 事件:
- input 事件在 值每次变更时立即触发(包括粘贴、删除、键盘输入),语义最准确;
- keyup 事件则在按键释放后触发,适合需区分按键类型的场景(如过滤非数字键)。
以下是修复后的核心逻辑(已适配动态创建流程):
for (let i = 0; i < OTP_LENGTH; i++) { const input = document.createElement('input'); input.type = 'text'; // ⚠️ 改为 text 更稳妥(number 类型在部分浏览器中可能触发 blur) input.maxLength = 1; input.dataset.otpPos = i; input.className = 'otp-input'; // ✅ 使用 input 事件替代 onchange input.addEventListener('input', function(e) { const target = e.target; const pos = parseInt(target.dataset.otpPos, 10); // 确保只输入单个数字(可选增强) target.value = target.value.replace(/[^0-9]/g, '').slice(0, 1); if (pos < OTP_LENGTH - 1 && target.value) { // 清空并聚焦下一个输入框(避免重复触发) const nextInput = textInputs[pos + 1]; nextInput.value = ''; nextInput.focus(); } }); // 可选:支持退格键回退(提升体验) input.addEventListener('keydown', function(e) { if (e.key === 'Backspace' && !this.value && this.previousElementSibling) { e.preventDefault(); this.previousElementSibling.focus(); this.previousElementSibling.select(); } }); otpContainer.appendChild(input); textInputs.push(input); }
⚠️ 注意事项:
- 避免重复 focus() 调用:确保 nextInput.focus() 前不触发其他干扰逻辑(如多次绑定事件);
- 禁用 type="number":因其在移动端可能引发软键盘异常、自动 blur 或值校验延迟,推荐统一用 type="text" + 正则过滤;
- ID 唯一性:原代码中所有输入框 id="otp-input" 违反 html 规范,应改用 data-* 属性或 class 定位;
- 移动端兼容性:input.focus() 在 ios safari 中需在用户手势上下文中调用(本例满足),但避免在异步回调(如 setTimeout)中调用。
总结:OTP 自动聚焦失败的本质是事件选型错误。将 change 替换为 input,配合合理的值清理与边界判断,即可稳定实现“输完即跳”的交互体验。