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

React输入框自定义光标随输入字符增多出现位置滞后问题

解决React自定义输入光标滞后问题

核心问题分析

自定义光标随输入字符增多逐渐滞后,本质是未处理输入框横向滚动偏移,且DOM更新与状态计算时机不匹配。当输入内容超出输入框宽度触发横向滚动时,光标位置计算未结合scrollLeft值修正,导致滚动后位置偏离真实光标位置。

关键修复步骤

  1. 加入滚动偏移修正
    计算光标左侧位置时,必须减去输入框的scrollLeft值,抵消滚动带来的可见区域偏移:

    const scrollLeft = inputRef.current.scrollLeft;
    const left = paddingLeft + borderLeftWidth + textWidth - scrollLeft;
    
  2. 用useLayoutEffect保证DOM同步
    替换useEffect为useLayoutEffect,确保每次DOM更新后立即计算光标位置,避免状态更新与DOM渲染的延迟差:

    useLayoutEffect(() => {
      updateCursorPosition();
    }, [text, cursorPosition]);
    
  3. 覆盖所有光标位置变化场景
    除输入内容变化外,监听点击、方向键移动等操作,确保光标位置实时更新:

    const handleClick = () => {
      setCursorPosition(inputRef.current.selectionStart);
    };
    
    const handleKeyDown = (e) => {
      if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
        setTimeout(() => {
          setCursorPosition(inputRef.current.selectionStart);
        }, 0);
      }
    };
    

完整修正后的核心代码片段

import { useState, useRef, useLayoutEffect } from "react";

const CustomInput = () => {
  const [text, setText] = useState("");
  const [cursorPosition, setCursorPosition] = useState(0);
  const inputRef = useRef(null);
  const measureRef = useRef(null);
  const cursorRef = useRef(null);

  const updateCursorPosition = () => {
    const input = inputRef.current;
    const measureElement = measureRef.current;
    const cursor = cursorRef.current;
    if (!input || !measureElement || !cursor) return;

    const paddingLeft = parseInt(getComputedStyle(input).paddingLeft);
    const borderLeftWidth = parseInt(getComputedStyle(input).borderLeftWidth);
    const scrollLeft = input.scrollLeft;

    measureElement.textContent = text.slice(0, cursorPosition);
    const textWidth = measureElement.offsetWidth;

    const left = paddingLeft + borderLeftWidth + textWidth - scrollLeft;
    cursor.style.left = `${left}px`;
  };

  useLayoutEffect(() => {
    updateCursorPosition();
  }, [text, cursorPosition]);

  const handleChange = (e) => {
    setText(e.target.value);
    setCursorPosition(e.target.selectionStart);
  };

  const handleClick = () => {
    setCursorPosition(inputRef.current.selectionStart);
  };

  const handleKeyDown = (e) => {
    if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
      setTimeout(() => {
        setCursorPosition(inputRef.current.selectionStart);
      }, 0);
    }
  };

  return (
    <div style={{ position: "relative" }}>
      <input
        ref={inputRef}
        value={text}
        onChange={handleChange}
        onClick={handleClick}
        onKeyDown={handleKeyDown}
        style={{
          fontSize: "16px",
          padding: "8px 12px",
          border: "1px solid #ccc",
          outline: "none",
          caretColor: "transparent",
          width: "300px",
        }}
      />
      <div
        ref={measureRef}
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          visibility: "hidden",
          whiteSpace: "pre",
          fontSize: "16px",
          padding: "8px 12px",
          border: "1px solid #ccc",
          opacity: 0,
        }}
      />
      <div
        ref={cursorRef}
        style={{
          position: "absolute",
          top: "8px",
          width: "2px",
          height: "18px",
          backgroundColor: "black",
          animation: "blink 1s infinite",
        }}
      />
      <style>{`
        @keyframes blink {
          0%, 50% { opacity: 1; }
          51%, 100% { opacity: 0; }
        }
      `}</style>
    </div>
  );
};

export default CustomInput;

额外注意事项

  • 确保测量元素的所有文本样式与输入框完全一致,包括font-family、font-size、font-weight、letter-spacing、word-spacing等,这些属性直接影响文本宽度计算。
  • 若为多行输入框,需额外处理换行后的垂直位置计算,单行场景可忽略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:00:08