React 中 input 输入框格式化时光标问题

4,110 阅读2分钟

以手机号输入格式化为例

  • 格式: 111 1111 111
  • 光标在最后输入是不会出现问题
  • 光标在中间输入是会出现问题的, 输入后光标会移动到最后
  • 删除遇到空格也是会出问题

效果展示

分析

  1. 格式化数据时, 一般在onChange方法中改变后重新设置 value, 相当于重新赋值, 所以光标会移到最后
  2. 想要解决问题, 需要重新给光标设置位置
  3. 通过分尝试, input 的值改变时正常情况, 光标会向后移动相应增加的位置, 但是格式化后新的值多了空格, 所以光标的位置, 会前移。经分析和尝试添加几个空格就回前移几位。

解决

首先记录变化前的光标位置

  • onKeyDown 事件中记录光标位置
  • onChange 事件中记录值变化后的光标位置
  • 通过光标位置获取到添加的字符串
  • 获取到添加的字符串中添加了几个空格, 光标移动几位

代码

const Input = () => {
  const title = '';

  const [value, setValue] = useState('1');
  const [position, setPosition] = useState(0);
  const inputEl = useRef(null);

  // 获取字符串中有多少空格
  const getNum = (str) => {
    let index = str.indexOf(' ');
    var num = 0;
    while (index !== -1) {
      index = str.indexOf(' ', index + 1);
      num ++
    }
    return num;
  }

  let posit = 0;

  // 记录值变化前的位置
  const handleOnKeyDown = ent => {
    // setPosition(inputEl.current.selectionStart);
    posit = inputEl.current.selectionStart;
  }

  const handleChange = ent => {
    const val = ent.target.value;

    let str = val.replace(/\s/g, '')
    str = `${ str.substring(0, 3) } ${ str.substring(3, 7) } ${  str.substring(7, 11) }`


    console.log('字符串: ', str);

    if (str.charAt(str.length - 1 ) === ' ') {
      str = str.substring(0, str.length - 1);
    }

    // 添加
    if (str.length > value.length) {
      console.log('onKeyDownP ==> ', posit);
      const len = str.length - value.length; // 值变化的长度
      const addStr = str.substring(posit, posit + len);
      console.log('添加的字符串: ', addStr);
      const step = getNum(addStr);
      setPosition(inputEl.current.selectionStart + step)
    }

    // 删除
    if (str.length < value.length) {
      console.log('删除');
      if (str.charAt(inputEl.current.selectionStart - 1) === ' ') {
        setPosition(inputEl.current.selectionStart - 1)
      }
      else {
        setPosition(inputEl.current.selectionStart)
      }
    }

    // 没有变化
    if(str.length === value.length) {
      setPosition(inputEl.current.selectionStart);
    }
    console.log('start: ==> ', inputEl.current.selectionStart);

    setValue(str);
  }

  useEffect(() => {
    console.log(" ==> ", position);
    inputEl.current.selectionStart = position;
    inputEl.current.selectionEnd = position;
  }, [position, value])

  return (
    <div>
      <input
        id="input"
        ref={inputEl}
        onKeyDown={handleOnKeyDown}
        placeholder="输入数据"
        type="text"
        onChange={handleChange}
        value={value}
      />
    </div>
  )
}
export default Input;

修改后效果