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

如何在富文本编辑器中实现跨多个HTML标签的选中文本高亮

如何在富文本编辑器中实现跨多个HTML标签的选中文本高亮

这个问题绝对是富文本编辑器开发中的经典痛点,我当初做类似功能的时候也卡了好一阵。核心难点就是原生DOM的选区(Selection)和范围(Range)API需要精细操作,才能处理跨标签的选中场景。下面我给你一步步拆解可行的原生JS解决方案:

首先得明确核心思路:我们需要把选中的文本片段,不管跨多少个标签,都拆分成「完全未选中」「部分选中」「完全选中」的节点,然后把所有选中的部分用带高亮类的<span>包裹起来。

第一步:获取并验证当前选区

先拿到用户的选中内容,判断是否有有效的选区:

const selection = window.getSelection();
// 如果没有选中内容或者选区是折叠的(只是光标),直接退出
if (selection.isCollapsed || selection.rangeCount === 0) return;
// 取第一个选区范围(一般用户只会有一个选区)
const range = selection.getRangeAt(0);

第二步:处理已有的高亮(避免嵌套)

如果之前已经有高亮的文本,直接在上面嵌套新的高亮会导致DOM结构混乱,所以先把已有的<span class="highlight">拆解掉,还原成原始节点:

function unwrapExistingHighlights(range) {
  // 找到选区范围内所有已存在的高亮元素
  const highlights = Array.from(range.commonAncestorContainer.querySelectorAll('.highlight'));
  highlights.forEach(highlight => {
    const parent = highlight.parentNode;
    // 把高亮元素的子节点逐个移到父节点中
    while (highlight.firstChild) {
      parent.insertBefore(highlight.firstChild, highlight);
    }
    // 删除空的高亮元素
    parent.removeChild(highlight);
  });
}

第三步:拆分并包裹选中的文本片段

这是最关键的一步,我们要处理两种核心情况:选中内容在单个节点内、跨多个节点(起始和结束节点部分选中,中间节点完全选中)。这里用splitText()方法拆分文本节点,把选中的部分单独分离出来:

function wrapSelectedRange(range) {
  // 创建高亮元素
  const highlightSpan = document.createElement('span');
  highlightSpan.className = 'highlight';

  // 情况1:选中内容跨多个节点
  if (range.startContainer !== range.endContainer) {
    // 1. 拆分起始节点,把选中的后半部分分离出来
    const startSplitNode = range.startContainer.splitText(range.startOffset);
    // 把分离出的起始选中部分放入高亮元素
    highlightSpan.appendChild(startSplitNode);

    // 2. 处理中间完全被选中的节点,直接移到高亮元素里
    let nextNode = startSplitNode.nextSibling;
    while (nextNode && nextNode !== range.endContainer) {
      // 先存下下一个节点,避免移动后丢失引用
      const tempNode = nextNode;
      nextNode = nextNode.nextSibling;
      highlightSpan.appendChild(tempNode);
    }

    // 3. 拆分结束节点,把选中的前半部分分离出来并放入高亮元素
    const endSplitNode = range.endContainer.splitText(range.endOffset);
    highlightSpan.appendChild(endSplitNode.previousSibling);

    // 4. 把高亮元素插入到DOM中合适的位置
    range.startContainer.parentNode.insertBefore(highlightSpan, startSplitNode.nextSibling);
  } 
  // 情况2:选中内容在单个节点内
  else {
    // 拆分起始位置,分离出选中的部分
    const selectedTextNode = range.startContainer.splitText(range.startOffset);
    // 拆分结束位置,把选中部分和未选中后半部分分开
    selectedTextNode.splitText(range.endOffset - range.startOffset);
    // 插入高亮元素并把选中部分放进去
    range.startContainer.parentNode.insertBefore(highlightSpan, selectedTextNode);
    highlightSpan.appendChild(selectedTextNode);
  }

  // 清除原有选区,避免后续操作冲突
  selection.removeAllRanges();
}

第四步:整合功能并绑定事件

把上面的函数整合起来,绑定到鼠标松开或者高亮按钮的点击事件上:

function applyTextHighlight() {
  const selection = window.getSelection();
  if (selection.isCollapsed || selection.rangeCount === 0) return;
  
  const range = selection.getRangeAt(0);
  // 先拆解已有高亮
  unwrapExistingHighlights(range);
  // 包裹新的高亮
  wrapSelectedRange(range);
}

// 示例:绑定到鼠标松开事件,用户选中后松开鼠标就触发高亮
document.addEventListener('mouseup', applyTextHighlight);

补充说明

  • splitText()方法只对文本节点有效,所以如果选中的是元素节点(比如整个<b>nervous</b>),代码会直接把这个元素移到高亮span里,这正是我们想要的效果。
  • 如果你需要支持取消高亮,可以做类似的反向操作:选中已高亮的文本,拆解掉高亮span即可。
  • 记得给.highlight类加CSS样式,比如background-color: yellow;,这样才能看到高亮效果。

备注:内容来源于stack exchange,提问作者Roy Hoffman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 11:05:28