如何实现动态创建的OTP输入框自动聚焦切换功能

本文详解为何otp输入框的change事件无法触发自动聚焦,以及如何通过input或keyup事件正确实现数

字输入后自动跳转到下一个输入框的功能。

在构建六位一次性密码(OTP)输入组件时,一个常见需求是:用户在某个输入框中输入一位数字后,焦点自动移至下一个输入框。然而,若使用 change 事件监听值变化并调用 focus(),往往失效——原因在于 change 事件仅在输入框失去焦点(blur)且值发生改变后才触发,而非实时响应每一次输入。这意味着用户尚未离开当前输入框时,change 根本不会被触发,自然无法执行后续的 focus() 操作。

正确的做法是改用 input 事件(推荐)或 keyup 事件:

  • ✅ input:在 每次变更时立即触发(包括键盘输入、粘贴、拖放等),语义准确、兼容性好(IE9+);
  • ✅ keyup:按键释放时触发,适合需区分按键行为的场景,但不捕获非键盘输入(如粘贴);
  • ❌ change:仅在 blur 后校验变更,不适用于实时跳转逻辑。

以下是修正后的核心代码示例(基于原逻辑优化):

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 属性(更可靠)
  input.dataset.otpPos = i;
  input.className = 'otp-input';

  // 实时监听输入
  input.addEventListener('input', function (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) {
      const nextInput = textInputs[position + 1];
      if (nextInput) {
        nextInput.focus();
      }
    }
  });

  // 可选:支持退格键回退(提升体验)
  input.addEventListener('keydown', function (e) {
    if (e.key === 'Backspace' && !this.value && this.previousElementSibling) {
      e.preventDefault();
      this.previousElementSibling.focus();
    }
  });

  textInputs.push(input);
  otpContainer.appendChild(input);
}

// 首个输入框初始聚焦
if (textInputs[0]) textInputs[0].focus();

关键注意事项:

  • 避免为多个输入框设置相同 id="otp-input"(ID 必须唯一),改用 data-* 属性或 class 进行标识;
  • type="number" 在部分移动端可能触发两次输入或阻止粘贴,建议统一用 type="text" 并手动校验数字;
  • 调用 focus() 前请确保目标元素已挂载到 DOM 且可聚焦(本例中因顺序创建,无此问题);
  • 若需支持粘贴多位数字(如用户粘贴 "123456"),可额外监听 paste 事件并分发到各输入框。

通过将事件从 change 切换至 input,即可实现流畅、可靠的 OTP 自动跳转体验,兼顾可用性与健壮性。