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
相关产品推荐
相关产品推荐

