如何为富文本编辑器(rich text editor)中选中的文本范围设置不同颜色
富文本编辑器选中范围设置文本颜色实现方案
直接对选中范围包裹
<span>标签失败的核心原因是:用户选中的范围大概率跨了多个已有DOM节点(比如包含加粗、斜体等已有样式的节点,或跨了多个段落块级节点),或是选中范围的起止位置落在某个文本节点的中间,直接整体包裹会生成非法的嵌套结构,破坏原有DOM树。
实现逻辑
- 第一步:获取当前选中的范围对象
调用window.getSelection()拿到用户选中实例,再通过getRangeAt(0)获取当前选中的Range对象,包含选中的起止节点、偏移量等信息。 - 第二步:拆分边界节点
对Range的起始节点和结束节点做拆分处理:如果起始偏移量不是0,就把起始文本节点从偏移量位置拆成两个独立文本节点,让选中范围的起点刚好是新文本节点的起始位置;同理处理结束节点,让选中范围的终点刚好是文本节点的结束位置。处理后整个选中范围覆盖的都是完整的独立文本节点,不会出现半选节点的情况。 - 第三步:遍历选中范围内的所有文本节点
用document.createTreeWalker创建文本节点遍历器,过滤出所有完全落在选中范围内的文本节点,避免处理到范围外的内容。 - 第四步:逐个包裹颜色样式标签
对每个遍历到的文本节点,优先检查父节点是否已经是带颜色样式的<span>,如果是直接修改父节点的color属性即可,避免重复嵌套;否则新建带对应颜色样式的<span>标签,将文本节点移入其中。 - 第五步:恢复用户选中状态
处理完成后重新将修改后的Range挂载到Selection实例上,保持用户的选中状态不丢失。
基础实现代码
function setSelectedTextColor(targetColor) { const selection = window.getSelection(); // 无选中内容直接返回 if (!selection.rangeCount) return; const activeRange = selection.getRangeAt(0); // 拆分起始边界节点 if (activeRange.startContainer.nodeType === Node.TEXT_NODE && activeRange.startOffset > 0) { activeRange.startContainer.splitText(activeRange.startOffset); activeRange.setStart(activeRange.startContainer.nextSibling, 0); } // 拆分结束边界节点 if (activeRange.endContainer.nodeType === Node.TEXT_NODE && activeRange.endOffset < activeRange.endContainer.length) { activeRange.endContainer.splitText(activeRange.endOffset); activeRange.setEnd(activeRange.endContainer, activeRange.endContainer.length); } // 创建文本节点遍历器 const textNodeWalker = document.createTreeWalker( activeRange.commonAncestorContainer, NodeFilter.SHOW_TEXT, { acceptNode: node => activeRange.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT } ); const selectedTextNodes = []; let currentNode = textNodeWalker.nextNode(); while (currentNode) { selectedTextNodes.push(currentNode); currentNode = textNodeWalker.nextNode(); } // 逐个节点应用颜色 selectedTextNodes.forEach(textNode => { const parentEl = textNode.parentElement; // 已有颜色span直接修改 if (parentEl.tagName === 'SPAN' && parentEl.style.color) { parentEl.style.color = targetColor; return; } // 新建颜色span const colorSpan = document.createElement('span'); colorSpan.style.color = targetColor; parentEl.insertBefore(colorSpan, textNode); colorSpan.appendChild(textNode); }); // 恢复选中状态 selection.removeAllRanges(); selection.addRange(activeRange); }
注意事项
- 如果你的编辑器需要支持撤销/重做功能,需要将本次所有DOM修改操作记录到历史操作栈中,不要直接修改DOM后不做记录。
- 处理完成后可以增加一步相邻同样式span的合并逻辑,避免生成过多冗余的DOM节点,优化渲染性能。
- 不推荐直接用原生
document.execCommand('foreColor')实现,该API已经被标记为废弃,且多数浏览器会生成非标准的<font>标签,样式可控性差。
内容的提问来源于stack exchange,提问作者noiseymur
相关产品推荐
相关产品推荐

