You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 手机号格式化输入框修改内容后光标跳转至末尾如何解决

问题根因

你当前的实现每次触发输入事件时,都会直接将格式化后的完整字符串覆盖到input的value上,浏览器更新输入框内容后会默认将光标定位到内容末尾,不会保留你编辑时的原始光标位置,因此在中间增删字符时会出现光标跳转问题。

修复思路

  1. 用useRef获取input的DOM实例,记录输入前的光标位置
  2. 计算光标位置之前的纯数字个数,匹配格式化后对应内容的光标偏移量
  3. 状态更新完成后,手动将光标重置到计算后的正确位置

修复后完整代码

import { useState, useRef } from "react";

function formatPhoneNumber(phone) {
  const area = phone.slice(0, 3);
  const first = phone.slice(3, 6);
  const second = phone.slice(6, 10);

  let formattedTelephoneNumber = phone;

  if (phone.length >= 1) {
    formattedTelephoneNumber = `(${formattedTelephoneNumber}`;
  }

  if (phone.length >= 4) {
    formattedTelephoneNumber = `(${area}) ${first}`;
  }

  if (phone.length >= 7) {
    formattedTelephoneNumber = `(${area}) ${first}-${second}`;
  }

  return formattedTelephoneNumber;
}

function getRawPhoneNumber(inputValue) {
  return [...inputValue].filter((char) => /\d/.test(char)).join("");
}

// 计算格式化后正确的光标位置
function getCorrectCaretPosition(formattedValue, rawDigitsBeforeCaret) {
  let digitCount = 0;
  for (let i = 0; i < formattedValue.length; i++) {
    if (/\d/.test(formattedValue[i])) {
      digitCount++;
    }
    if (digitCount === rawDigitsBeforeCaret) {
      return i + 1;
    }
  }
  return formattedValue.length;
}

export default function App() {
  const [value, setValue] = useState("");
  const inputRef = useRef(null);

  const handleInputChange = (e) => {
    const input = e.target;
    // 记录当前光标前的纯数字个数
    const caretPosition = input.selectionStart;
    const valueBeforeCaret = input.value.slice(0, caretPosition);
    const rawDigitsBeforeCaret = getRawPhoneNumber(valueBeforeCaret).length;

    const phoneNumber = getRawPhoneNumber(input.value);
    const formatted = formatPhoneNumber(phoneNumber);
    setValue(formatted);

    // 等DOM更新完成后重置光标位置
    requestAnimationFrame(() => {
      if (!inputRef.current) return;
      const newCaretPosition = getCorrectCaretPosition(formatted, rawDigitsBeforeCaret);
      inputRef.current.setSelectionRange(newCaretPosition, newCaretPosition);
    });
  };

  return (
    <div className="App">
      <input ref={inputRef} value={value} onChange={handleInputChange} />
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Joji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 14:06:03