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

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)
} 

问题原因

  1. 组件重渲染导致光标丢失:调用setContent更新state后,组件会重新渲染,原可编辑div被重新创建,光标默认复位到开头。
  2. 事件绑定错误:此前的() => onChange写法没有传递事件对象,导致onInput无法触发。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:16:33