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

React中切换contentEditable为true后无法即时聚焦文本的问题

解决React中contentEditable元素点击斜体文本无法聚焦定位的问题

在React组件中,通过showSpan状态控制可编辑span的显示隐藏。点击div触发mousedown后显示span,普通文本点击能正常聚焦到对应位置,但点击<i>标签内的文本时光标无法定位;同时需要确保仅在聚焦状态下显示span。

关键修复点

  • 统一状态变量名:修正原代码中状态定义与更新函数不匹配的问题,保持命名一致性。
  • 保存点击位置信息:在mousedown事件中记录点击的DOM节点和偏移量,为后续光标定位提供依据。
  • 监听状态变化手动聚焦:使用useEffect监听showSpan状态切换,在span渲染完成后手动聚焦,并利用Selection API恢复光标到点击位置。
  • 确保显示逻辑准确:通过blur事件同步状态,保证span仅在聚焦状态下显示。

完整代码实现

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

export default function App() {
  const [showSpan, setShowSpan] = useState(false);
  const spanRef = useRef(null);
  const clickPositionRef = useRef({ node: null, offset: 0 });

  const handleMouseDown = (e) => {
    // 记录点击位置的节点与偏移量
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      clickPositionRef.current = {
        node: range.startContainer,
        offset: range.startOffset
      };
    }
    setShowSpan(true);
    // 阻止默认行为,避免浏览器选中原文本
    e.preventDefault();
  };

  useEffect(() => {
    if (showSpan && spanRef.current) {
      // 等待DOM更新完成后执行聚焦与光标定位
      setTimeout(() => {
        spanRef.current.focus();
        const selection = window.getSelection();
        selection.removeAllRanges();
        
        // 在span中匹配原点击的对应节点
        const targetNode = findCorrespondingNode(spanRef.current, clickPositionRef.current.node);
        if (targetNode) {
          const range = document.createRange();
          range.setStart(targetNode, clickPositionRef.current.offset);
          range.setEnd(targetNode, clickPositionRef.current.offset);
          selection.addRange(range);
        }
      }, 0);
    }
  }, [showSpan]);

  // 递归查找span内与原点击节点对应的目标节点
  const findCorrespondingNode = (parent, target) => {
    if (parent.textContent === target.textContent && parent.tagName === target.tagName) {
      return parent;
    }
    for (let child of parent.children) {
      const result = findCorrespondingNode(child, target);
      if (result) return result;
    }
    if (parent.nodeType === Node.TEXT_NODE && parent.textContent === target.textContent) {
      return parent;
    }
    return null;
  };

  return (
    <>
      <div onMouseDown={handleMouseDown}>
        {showSpan ? (
          <span
            ref={spanRef}
            contentEditable={true}
            onFocus={() => console.log("focus")}
            onBlur={() => setShowSpan(false)}
          >
            Text can focus first time
            <br />
            <i>Text cannot focus first time</i>
          </span>
        ) : (
          <>
            Text can focus first time
            <br />
            <i>Text cannot focus first time</i>
          </>
        )}
      </div>
    </>
  );
}

代码说明

  1. 状态与Ref管理:用showSpan控制span显示,spanRef获取可编辑元素实例,clickPositionRef缓存点击位置的节点信息。
  2. mousedown事件处理:捕获当前点击的Range信息并保存,阻止默认行为避免原文本被选中,触发状态更新显示span。
  3. 光标定位逻辑:在span渲染完成后,通过递归匹配找到对应节点,使用Range和Selection API将光标精准定位到点击位置。
  4. 节点匹配函数:通过文本内容和标签类型匹配,确保在span的DOM结构中找到与原点击位置对应的节点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 13:30:51