修改高亮文本颜色时调用Range的surroundContents触发InvalidStateError如何解决
错误产生原因
- 你调用
Range.surroundContents()API时触发了该接口的原生限制:该API要求选中的Range范围必须完整包含所有内部节点,不能出现节点被部分选中的情况,否则就会抛出对应错误。 - 你给文本添加高亮时,原本的纯文本已经被带背景色的
<span>类节点包裹,当用户选中的内容跨了高亮节点和普通文本节点、或者仅选中了高亮节点内的部分文本时,Range就会出现部分选中非文本节点的情况,直接调用surroundContents()就会报错。
修复方案
方案1:先拆分选中区域节点再统一高亮(兼容性最优)
核心思路是把跨节点的选中区域拆成独立文本片段,清除旧高亮后再统一添加新高亮,从根源上规避接口限制,示例核心代码如下:
// 提取Range覆盖的所有文本节点 function getTextNodesInRange(range) { const textNodes = []; const walker = document.createTreeWalker( range.commonAncestorContainer, NodeFilter.SHOW_TEXT, { acceptNode: node => range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT } ); let node; while (node = walker.nextNode()) textNodes.push(node); return textNodes; } // 高亮处理逻辑 function updateHighlight(color) { const selection = window.getSelection(); if (selection.rangeCount === 0) return; const range = selection.getRangeAt(0); if (range.collapsed) return; const textNodes = getTextNodesInRange(range); // 拆分首尾节点,保证选中部分为独立文本节点 if (textNodes.length) { const firstNode = textNodes[0]; if (range.startOffset > 0) textNodes[0] = firstNode.splitText(range.startOffset); const lastNode = textNodes.at(-1); const endOffset = range.endOffset - (lastNode === firstNode ? range.startOffset : 0); if (endOffset < lastNode.length) lastNode.splitText(endOffset); } // 替换旧高亮,添加新高亮 textNodes.forEach(node => { // 清除旧的高亮标签 if (node.parentElement.classList.contains('highlight')) { node.parentElement.replaceWith(node); } // 包裹新高亮标签 const highlightSpan = document.createElement('span'); highlightSpan.classList.add('highlight'); highlightSpan.style.backgroundColor = color; highlightSpan.textContent = node.textContent; node.replaceWith(highlightSpan); }); selection.removeAllRanges(); }
方案2:前置校验Range合法性(实现成本最低)
如果不需要支持跨节点修改高亮,可以提前判断Range是否符合调用条件,不符合的情况下直接终止操作:
const selection = window.getSelection(); const range = selection.getRangeAt(0); // 高版本浏览器原生校验方法 if (range.canSurroundContents && !range.canSurroundContents()) { alert('请选择完整的文本段落后再修改高亮颜色'); return; } // 低版本浏览器兼容校验 if (range.startContainer.nodeType !== Node.TEXT_NODE || range.endContainer.nodeType !== Node.TEXT_NODE || range.startContainer.parentElement !== range.endContainer.parentElement) { alert('请选择完整的文本段落后再修改高亮颜色'); return; } // 后续执行原有的surroundContents逻辑
内容的提问来源于stack exchange,提问作者Md Rezaul Karim
相关产品推荐
相关产品推荐

