React输入框自定义光标随输入字符增多出现位置滞后问题
解决React自定义输入光标滞后问题
核心问题分析
自定义光标随输入字符增多逐渐滞后,本质是未处理输入框横向滚动偏移,且DOM更新与状态计算时机不匹配。当输入内容超出输入框宽度触发横向滚动时,光标位置计算未结合scrollLeft值修正,导致滚动后位置偏离真实光标位置。
关键修复步骤
加入滚动偏移修正
计算光标左侧位置时,必须减去输入框的scrollLeft值,抵消滚动带来的可见区域偏移:const scrollLeft = inputRef.current.scrollLeft; const left = paddingLeft + borderLeftWidth + textWidth - scrollLeft;用
useLayoutEffect保证DOM同步
替换useEffect为useLayoutEffect,确保每次DOM更新后立即计算光标位置,避免状态更新与DOM渲染的延迟差:useLayoutEffect(() => { updateCursorPosition(); }, [text, cursorPosition]);覆盖所有光标位置变化场景
除输入内容变化外,监听点击、方向键移动等操作,确保光标位置实时更新: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
相关产品推荐
相关产品推荐

