高亮文本的主流方式

高亮文本的主流方式


高亮文本乍一看简单,只需要改一下背景为黄色,但其实很复杂。比如下面这个例子,高亮的部分横跨多个元素并包含元素下面的部分文本。

Highlights across elements

下面从几种常见的文本高亮实现方式讲起,结合优缺点和实际使用场景,梳理一下主流技术方案。

CSS Highlight API —— 现代且高效的文本高亮方式

这是目前实现文本高亮的最佳选择,简单易用且性能优越。唯一的缺点是浏览器需要较新版本支持,不过在2025年的当下所有主流浏览器也都支持了。

Highlight 对象

首先你需要为每个高亮区域创建一个 Range 对象。 Range 是一个古已有之的API,代表DOM树上一段连续的区域,如果你不清楚的话就自己补补课。

const range = new Range();
range.setStart(someNode, startOffset);
range.setEnd(someNode, endOffset);

然后添加到 Highlight 对象。因为高亮可以有多个不连续的区域,所以一个 Highlight 对象也可以有若干个 Range。

const highlight = new Highlight(range1, range2, ..., rangeN);

它是一个类似 Set 的对象,所以可以用 add, delete 等方法修改包含的 Range。

CSS.highlights

然后将 Highlight 对象添加到 CSS.highlights 里。它是一个类似Map的对象,添加删除成员的方法和Map一样,比如 get, set, delete 等,遍历成员的方法也是一样的。

就和 Map一样,在添加 Highlight 的时候需要给他一个 key,当作这个 highlight 的名字了。这个名字有很大的用处,下面会看到,我们用这个名字来设置高亮的样式,比如是绿色背景。所以同一个 Highlight 对象的高亮样式是相同的。如果你想要再用蓝色高亮另一段文本,那么要新建一个 highlight。

CSS.highlights.set("my-highlight", highlight);

定义伪元素 ::highlight

接下来就轮到css样式登场了,用 ::highlight(高亮的名字) 伪元素就可以定义高亮的样式啦。

::highlight(my-highlight) {
  background-color: yellow;
  color: black;
}

注意:只能使用少数几种 CSS 属性,例如 background-colorcolorcursor 等,详见规范

动态修改高亮范围

CSS.highlights 就像一个 Map,它有类似的方法来更新 Highlight

  • CSS.highlights.clear()
  • set(highlightName, Highlight)
  • delete(highlightName)

Highlight 对象就像 Set,有类似的方法来更新 Range

  • Highlight.add(range)
  • delete(range)

修改文本元素的样式

通过遍历 DOM 找到目标文本节点,直接修改其背景样式来实现高亮。

  • 需要对 DOM 进行修改,浏览器会重新布局,性能开销较大。
  • 实现跨元素高亮(即高亮范围跨越多个元素)较为复杂。 Highlights across elements
  • 适合高亮内容相对固定,且范围较小的场景。

覆盖额外图层渲染高亮,不改动文本 DOM

创建一个透明的叠加层,计算目标文本的屏幕位置,然后在叠加层绘制半透明的高亮。

定位高亮区域

  • 使用 Element.getClientRects()getBoundingClientRect() 获取文本在页面上的坐标。
  • 处理多行文本时,每行对应一个矩形区域,需分别绘制高亮块。
  • 窗口尺寸变动或字体大小变化时需要重新计算高亮位置,性能可能受影响。

高亮的绘制方式

DIV 作为叠加层

使用绝对定位的父容器和半透明背景的子元素覆盖文本。 例如 Monaco 编辑器就是用此方案实现同词高亮。

<div class="editor" style="position: relative;">
  <div class="overlay" style="position: absolute; height: 0;">
    <div
      class="highlight"
      style="top:0; left:57px; width:23px; background-color: rgba(173,214,255,0.15);"
    ></div>
    <!-- 添加更多 highlight 的区域 -->
  </div>
  <div class="content">
    <!-- 文本内容 -->
  </div>
</div>
  • overlay 的 position 是 absolute,同时保证父元素不是 position: static,这样 absolute 才能生效。这可以让内部的高亮元素定位到任意位置。
  • 想要在文本上方,只需要在HTML里将 overlay 放在文本容器之前。overlay 的高度为 0,避免影响下方文本的交互。
  • 内部添加元素,background-color 设为半透明的高亮颜色,定位到高亮文本的位置,设置 width
  • Monaco 编辑器(VSCode就是基于它的)用的这种办法。见官方演示,选中某个关键字后,相同的关键字都会高亮。而查看 HTML 会发现,这些关键字并没有改变样式,而是在编辑器界面上有一个 Overlay,里面绘制了高亮的半透明颜色(见 class="view-overlays" 的 div)

SVG 作为叠加层

  • overlay 设置为和页面长宽一样
  • 内部添加 <rect>,然后到高亮文本的位置绘制
  • foliate-js 用的这个办法。见 overlayer.js

利用 Selection API 和 ::selection 伪元素 —— 浏览器默认的文本选择高亮

浏览器本身会高亮选中的文本,所以控制选区就可以变相实现高亮。样式可通过 CSS 的 ::selection 伪元素自定义。

const range = new Range();
range.setStart(parentNode, startOffset);
range.setEnd(parentNode, endOffset);

// 设置选区
const selection = document.getSelection();
selection.removeAllRanges();
selection.addRange(range);
::selection {
  background-color: #c3c2c2;
  color: white;
}

这个方法比较取巧,很简单而且浏览器支持广泛,但有一个重大缺陷:因为同时只能有一个选区,编程选区和用户实际选区会互相覆盖冲突。所以只适用于满足以下条件的场景

  • 用户不能选择文本
  • 只有一段连续的高亮文本
  • 需要支持老旧的浏览器
  • 你想偷懒,不想用其他方法