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

React中用react-contenteditable在可编辑div光标处插入span标签

在React中用react-contenteditable实现指定位置插入不可编辑Span

步骤1:安装依赖

首先安装react-contenteditable包:

npm install react-contenteditable
# 或者用yarn
yarn add react-contenteditable

步骤2:实现核心组件

下面是完整的组件代码,包含所有需求逻辑:

import React, { useState, useRef, useEffect } from 'react';
import ContentEditable from 'react-contenteditable';

const EditableText = () => {
  const [html, setHtml] = useState('请输入文本,点击按钮插入特殊标记');
  const editableRef = useRef(null);

  // 获取当前光标位置
  const getCaretPosition = () => {
    const selection = window.getSelection();
    if (selection.rangeCount === 0) return null;
    const range = selection.getRangeAt(0);
    const preCaretRange = range.cloneRange();
    preCaretRange.selectNodeContents(editableRef.current);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    return preCaretRange.toString().length;
  };

  // 设置光标到指定位置
  const setCaretPosition = (pos) => {
    const selection = window.getSelection();
    const range = document.createRange();
    
    const traverse = (node, currentPos) => {
      if (node.nodeType === Node.TEXT_NODE) {
        if (currentPos + node.length >= pos) {
          range.setStart(node, pos - currentPos);
          range.setEnd(node, pos - currentPos);
          return true;
        } else {
          return currentPos + node.length;
        }
      } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SPAN') {
        if (currentPos + 1 >= pos) {
          // 把不可编辑span视为单个字符,光标移到其后方
          range.setStartAfter(node);
          range.setEndAfter(node);
          return true;
        } else {
          return currentPos + 1;
        }
      } else {
        let newPos = currentPos;
        for (let child of node.childNodes) {
          const result = traverse(child, newPos);
          if (result === true) return true;
          newPos = result;
        }
        return newPos;
      }
    };
    traverse(editableRef.current, 0);
    selection.removeAllRanges();
    selection.addRange(range);
  };

  // 插入不可编辑的span
  const insertFixedSpan = () => {
    const selection = window.getSelection();
    // 有选中内容则不执行插入
    if (selection.toString().length > 0) return;

    const caretPos = getCaretPosition();
    if (caretPos === null) return;

    // 定义不可编辑span,contentEditable设为false确保无法修改
    const spanHtml = '<span contentEditable="false" style="color: red; margin: 0 2px;">★</span>';
    const newHtml = html.slice(0, caretPos) + spanHtml + html.slice(caretPos);
    setHtml(newHtml);

    // 插入后将光标移到span后方
    useEffect(() => {
      setTimeout(() => {
        setCaretPosition(caretPos + 1);
      }, 0);
    }, [newHtml]);
  };

  return (
    <div>
      <ContentEditable
        ref={editableRef}
        html={html}
        onChange={(e) => setHtml(e.target.value)}
        // 限制仅允许纯文本和span标签
        allowedTags={['span']}
        style={{
          border: '1px solid #ccc',
          padding: '8px',
          minHeight: '100px'
        }}
      />
      <button onClick={insertFixedSpan} style={{ marginTop: '10px' }}>
        插入特殊标记
      </button>
    </div>
  );
};

export default EditableText;

关键逻辑说明

  • 不可编辑Span:给span设置contentEditable="false",用户无法修改其内容,且在contentEditable容器中会被视为单个可跳过的“字符”。
  • 禁止选中插入:insertFixedSpan函数中先判断选中内容长度,若大于0则直接返回,避免在选中文本区域插入内容。
  • 光标位置维护:通过getCaretPosition记录插入前的光标位置,插入后用setCaretPosition将光标移动到新插入span的后方,保证编辑连贯性。
  • 内容限制:通过allowedTags={['span']}确保容器中仅能存在纯文本和span标签,符合需求。

可选样式优化

给不可编辑span添加样式,提升辨识度:

.fixed-span {
  background: #ffebee;
  padding: 0 4px;
  border-radius: 4px;
  user-select: none; /* 禁止选中span内容 */
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:22:45