React文本高亮功能异常:嵌套标签、Range错误及取消高亮求助
React文本高亮功能的问题修复方案
问题梳理
- 已高亮文本“quick fox jumps”后,再次选中“fox”会生成嵌套
<mark>标签,期望保持为<mark>quick fox jumps</mark> - 选中“jumps over”时抛出错误:
Uncaught DOMException: Failed to execute 'surroundContents' on 'Range': The Range has partially selected a non-Text node. - 缺少取消已高亮文本的功能
解决方案
1. 避免嵌套<mark>标签
处理选区前先检查范围内是否存在已有的<mark>元素,若有则先将这些元素的内容还原为普通文本,再统一重新包裹,杜绝嵌套情况。
2. 解决跨节点选区的surroundContents错误
surroundContents要求选区必须完全包含非文本节点,不能部分选中。改用extractContents提取选区内容为文档片段,再将片段包裹进<mark>后插入原位置,兼容跨节点的选区场景。
3. 实现取消高亮功能
判断当前选中文本是否已被<mark>包裹:若是,则移除<mark>标签并还原文本;若否则执行高亮操作。
修复后的完整代码
import "./styles.css"; import { useState, useRef } from "react"; export default function App() { const selectedRef = useRef(null); const handleSelection = () => { const selection = window.getSelection(); if (selection.rangeCount === 0 || selection.isCollapsed) return; const range = selection.getRangeAt(0); const selectedText = range.toString().trim(); if (!selectedText) return; // 检查选区是否包含已高亮的mark元素 const markElements = Array.from(range.commonAncestorContainer.querySelectorAll('mark')); const isHighlighted = markElements.some(mark => range.intersectsNode(mark)); if (isHighlighted) { // 取消高亮:移除mark标签,合并文本节点 markElements.forEach(mark => { const parent = mark.parentNode; while (mark.firstChild) { parent.insertBefore(mark.firstChild, mark); } parent.removeChild(mark); }); // 合并相邻文本节点,避免冗余空白 const parent = selectedRef.current; const childNodes = Array.from(parent.childNodes); childNodes.forEach((node, index) => { if (node.nodeType === Node.TEXT_NODE && index > 0) { const prevNode = childNodes[index - 1]; if (prevNode.nodeType === Node.TEXT_NODE) { prevNode.textContent += node.textContent; parent.removeChild(node); } } }); } else { // 处理跨节点选区,替代surroundContents避免报错 const fragment = range.extractContents(); const mark = document.createElement("mark"); mark.appendChild(fragment); range.insertNode(mark); } // 清除选区,避免重复触发 selection.removeAllRanges(); }; return ( <> <p onMouseUp={handleSelection} ref={selectedRef}> A quick fox jumps over a lazy dog. </p> </> ); }
代码说明
- 防嵌套高亮:通过
range.intersectsNode检测选区与已有<mark>的重叠情况,已高亮则先还原再处理。 - 跨节点兼容:用
extractContents提取选区内容为片段,再包裹<mark>插入,避开surroundContents的限制。 - 取消高亮逻辑:遍历目标
<mark>元素,将内部内容移至父节点后删除标签,同时合并相邻文本节点保持结构整洁。
内容的提问来源于stack exchange,提问作者Ram Chander
相关产品推荐
相关产品推荐

