React中contentEditable Div触发onInput时光标跳至前端问题求助
解决React中contentEditable输入时光标跳至开头的问题
问题场景
在React项目中使用contentEditable实现可编辑div时,触发onInput事件后光标会跳转到div前端(例如输入'fruit'会变成'tiurf')。当前实现代码:
<div className="min-w-[600px] min-h-[36.8px]"> <div id={`editableDiv-${Object.keys(item)}-${index}`} className="p-3" contentEditable suppressContentEditableWarning onInput={(e) => onChange(e)} > {Object.values(item)} </div> </div>
此前尝试另一种写法时光标正常,但onInput事件完全不触发:
<div className="min-w-[600px] min-h-[36.8px]"> <div id={`editableDiv-${Object.keys(item)}-${index}`} className="p-3" contentEditable suppressContentEditableWarning onInput={() => onChange} > {Object.values(item)} </div> </div>
关联的onChange函数:
const onChange = (e: any) => { console.log(1) let targetId = e.currentTarget.id let objectContent = e.currentTarget.textContent let checkIndex = parseInt(targetId.split('-')[2]) let checkTitle = targetId.split('-')[1] let newArr = [...content] newArr[checkIndex][checkTitle] = objectContent setContent(newArr) updateData(content) }
问题原因
- 组件重渲染导致光标丢失:调用
setContent更新state后,组件会重新渲染,原可编辑div被重新创建,光标默认复位到开头。 - 事件绑定错误:此前的
() => onChange写法没有传递事件对象,导致onInput无法触发。 - state更新异步问题:
updateData(content)中使用的是旧的state值,因为React state更新是异步的。
解决方案
1. 保存并恢复光标位置
利用Selection和Range API,在更新state前记录光标位置,DOM更新完成后恢复:
const onChange = (e: any) => { const target = e.currentTarget; // 保存当前光标位置 const selection = window.getSelection(); const range = selection?.getRangeAt(0); const startOffset = range?.startOffset || 0; const targetId = target.id; const objectContent = target.textContent; const checkIndex = parseInt(targetId.split('-')[2]); const checkTitle = targetId.split('-')[1]; // 不可变更新state(避免直接修改原对象) const newArr = [...content]; newArr[checkIndex] = { ...newArr[checkIndex], [checkTitle]: objectContent }; setContent(newArr); // 延迟恢复光标,确保DOM已更新 setTimeout(() => { const editableElement = document.getElementById(targetId); if (editableElement && selection && range) { selection.removeAllRanges(); const newRange = document.createRange(); // 确保元素有子节点再设置偏移 const textNode = editableElement.firstChild || document.createTextNode(''); newRange.setStart(textNode, Math.min(startOffset, textNode.length)); newRange.collapse(true); selection.addRange(newRange); } }, 0); };
2. 修复state异步更新问题
不要直接在onChange中使用旧state调用updateData,改用useEffect监听state变化:
import { useEffect } from 'react'; // ... useEffect(() => { updateData(content); }, [content, updateData]);
3. 修正事件绑定
直接传递onChange函数,React会自动注入事件对象:
<div id={`editableDiv-${Object.keys(item)}-${index}`} className="p-3" contentEditable suppressContentEditableWarning onInput={onChange} > {Object.values(item)} </div>
4. 优化列表渲染的稳定性
如果是在列表中渲染可编辑div,确保外层元素设置稳定的key(尽量不用index,用数据唯一标识),避免不必要的DOM重建:
{content.map((item, index) => ( <div key={item.id /* 用数据的唯一ID代替index */} className="min-w-[600px] min-h-[36.8px]"> <div id={`editableDiv-${Object.keys(item)}-${item.id}`} className="p-3" contentEditable suppressContentEditableWarning onInput={onChange} > {Object.values(item)} </div> </div> ))}
内容的提问来源于stack exchange,提问作者Sanket Sharma
相关产品推荐
相关产品推荐

