在软件中设置文本范围的样式是一项非常有用的功能。值得庆幸的是,我们期待着 CSS 自定义高亮 API,因为它代表了网页上设置文本范围样式的未来。

举个例子:如果您曾经使用过 Google Docs、Word 或 Dropbox Paper 等文本编辑软件,您会发现它们可以检测拼写和语法错误,并在下方显示漂亮的小波浪下划线以引起注意。像 VS Code 这样的代码编辑器对代码错误也执行相同的操作。

文本高亮的另一个非常常见的用例是搜索和高亮显示,您可以在其中使用文本输入框,输入内容会在页面上搜索匹配的结果,并将其高亮显示。现在尝试在您的网络浏览器中按Ctrl
/⌘
+ F
,然后键入本文中的某些文本。

浏览器本身通常会处理这些样式情况。可编辑区域(如<textarea>
)会自动获得拼写波浪线。查找命令会自动突出显示找到的文本。
但是,当我们想自己进行这种样式设置时该怎么办?在网络上执行此操作一直是一个常见问题。它可能让很多人花费了比应该花费更多的时间。
这不是一个简单的解决问题。我们不仅仅是用一个带有类的<span>
来包装文本并应用一些 CSS。实际上,这需要能够正确突出显示任意复杂 DOM 树中的多个文本范围,并可能跨越 DOM 元素的边界。
有两个常见的解决方案,包括
- 设置文本范围伪元素的样式,以及
- 创建您自己的文本高亮系统。
我们将首先回顾它们,然后看看即将推出的能够改变这一切的CSS 自定义高亮 API。但是,如果您
潜在解决方案 #1:可设置样式的文本范围
可能最著名的可设置样式的文本范围是用户选择。当您使用指向设备在网页中选择一段文本时,会自动创建一个Selection
对象。实际上,现在尝试在此页面上选择文本,然后在 DevTools 控制台中运行document.getSelection()
。您应该会看到有关所选文本的位置信息。

事实证明,您也可以通过 JavaScript 以编程方式创建文本选择。这是一个示例
// First, create a Range object.
const range = new Range();
// And set its start and end positions.
range.setStart(parentNode, startOffset);
range.setEnd(parentNode, endOffset);
// Then, set the current selection to this range.
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
难题的最后一块是设置此范围的样式。CSS 有一个名为::selection
的伪元素可以做到这一点,并且在所有浏览器中都受支持。
::selection {
background-color: #f06;
color: white;
}
这是一个使用此技术逐个突出显示页面中所有单词的示例
除了::selection
伪元素之外,还有许多其他伪元素
::target-text
选择浏览器中已滚动到的文本,前提是浏览器支持滚动到文本功能。(MDN)::spelling-error
选择浏览器标记为包含拼写错误的文本。(MDN)::grammar-error
选择浏览器标记为包含语法错误的文本。(MDN)
不幸的是,浏览器的支持情况并不理想,尽管这些范围本身很有用,但它们不能用于设置自定义文本的样式,而只能设置浏览器预定义的样式。
因此,用户文本选择很好,因为它相对易于实施,并且不会更改页面的 DOM。实际上,Range
对象本质上是页面中段落的坐标,而不是需要创建才能存在的 HTML 元素。
但是,一个主要的缺点是,创建选择会重置用户已手动选择的任何内容。尝试在上面的演示中选择文本以测试这一点。您会看到,代码将选择移动到其他位置后,它会消失。
潜在解决方案 #2:自定义高亮系统
如果您使用Selection
对象不足以满足您的需求,那么此第二个解决方案几乎是您唯一的选择。此解决方案围绕着自己完成所有操作,使用 JavaScript 在您希望高亮显示出现的位置插入新的 HTML 元素。
不幸的是,这意味着需要编写和维护更多的 JavaScript 代码,更不用说每次高亮显示更改时都强制浏览器重新创建页面的布局。此外,还有一些复杂的极端情况,例如,当您想突出显示跨越多个 DOM 元素的文本时。

有趣的是,CodeMirror 和Monaco(为 VS Code 提供支持的 JavaScript 文本编辑器库)有自己的高亮逻辑。它们使用一种略有不同的方法,其中高亮显示包含在 DOM 树的单独部分中。文本行和高亮显示的片段在 DOM 中的两个不同位置呈现,然后相互定位。如果您检查包含文本的 DOM 子树,则不会有任何高亮显示。这样,就可以重新呈现高亮显示,而不会影响文本行,也不必在其中引入新元素。
总的来说,感觉缺少一个浏览器驱动的突出显示功能。某些功能可以帮助解决所有这些缺点(不干扰用户文本选择、支持多选、代码简单)并且比自定义解决方案更快。
幸运的是,这就是我们今天要讨论的内容!
进入 CSS 自定义高亮 API
CSS 自定义高亮 API 是一项新的 W3C 规范(目前处于工作草案状态),它使您可以从 JavaScript 设置任意文本范围的样式!这里的方法与我们之前回顾的用户文本选择技术非常相似。它为开发人员提供了一种方法,可以通过 JavaScript 创建任意范围,然后使用 CSS 设置其样式。
创建文本范围
第一步是创建您要突出显示的文本范围。这可以通过在 JavaScript 中使用Range
来完成。因此,就像我们在设置当前选择时所做的那样
const range = new Range();
range.setStart(parentNode, startOffset);
range.setEnd(parentNode, endOffset);
值得注意的是,如果作为第一个参数传递的节点是文本节点还是其他节点,则setStart
和setEnd
方法的工作方式有所不同。对于文本节点,偏移量对应于节点中的字符数。对于其他节点,偏移量对应于父节点中的子节点数。
同样值得注意的是,setStart
和setEnd
不是描述范围开始和结束位置的唯一方法。查看Range
类上可用的其他方法以查看其他选项。
创建高亮显示
第二步包括为上一步骤中创建的范围创建Highlight
对象。Highlight
对象可以接收一个或多个Range
。因此,如果您想以完全相同的方式突出显示许多文本片段,则可能应该创建一个Highlight
对象,并使用与这些文本片段对应的所有Range
对其进行初始化。
const highlight = new Highlight(range1, range2, ..., rangeN);
但是您也可以根据需要创建任意数量的Highlight
对象。例如,如果您正在构建一个协作文本编辑器,其中每个用户都有不同的文本颜色,那么您可以为每个用户创建一个Highlight
对象。然后,可以像我们接下来将看到的那样,对每个对象设置不同的样式。
注册高亮显示
现在,Highlight
对象本身不会执行任何操作。它们首先需要在称为高亮注册表的内容中注册。这是通过使用CSS 高亮 API完成的。注册表的工作原理类似于一个映射,您可以在其中通过为高亮显示指定名称来注册新高亮显示,以及删除高亮显示(甚至清除整个注册表)。
以下是注册单个高亮显示的方法。
CSS.highlights.set('my-custom-highlight', highlight);
其中my-custom-highlight
是您选择的名称,highlight
是上一步中创建的Highlight
对象。
样式重点
最后一步是实际设置已注册高亮的样式。这是通过新的 CSS ::highlight()
伪元素完成的,使用你在注册 Highlight
对象时选择的名称(在我们上面的示例中为 my-custom-highlight
)。
::highlight(my-custom-highlight) {
background-color: yellow;
color: black;
}
值得注意的是,就像 ::selection
一样,只有一部分 CSS 属性可以与 ::highlight()
伪元素一起使用。
background-color
caret-color
color
cursor
fill
stroke
stroke-width
text-decoration
(这可能仅在规范的版本 2 中受支持)text-shadow
更新高亮
有多种方法可以更新页面上的高亮文本。
例如,你可以使用 CSS.highlights.clear()
完全清除高亮注册表,然后从头开始。或者,你也可以更新底层范围,而无需重新创建任何对象。为此,再次使用 range.setStart
和 range.setEnd
方法(或任何其他 Range
方法),浏览器将重新绘制高亮。
但是,Highlight
对象的工作方式类似于 JavaScript Set
,这意味着你还可以使用 highlight.add(newRange)
将新的 Range
对象添加到现有的 Highlight
中,或者使用 highlight.delete(existingRange)
删除 Range
。
第三,你还可以从 CSS.highlights
注册表中添加或删除特定的 Highlight
对象。由于此 API 的工作方式类似于 JavaScript Map
,因此你可以set
和 delete
来更新当前注册的 Highlight
。
浏览器支持
CSS 自定义高亮 API 的规范相对较新,并且它在浏览器中的实现仍不完整。因此,尽管这将成为 Web 平台非常有用的补充,但它还没有完全准备好投入生产使用。
Microsoft Edge 团队目前正在 Chromium 中实现 CSS 自定义高亮 API。事实上,可以通过启用实验性 Web 平台功能标志(在 about:flags
下),立即在 Canary 版本中使用此功能。目前还没有关于该功能何时将在 Chrome、Edge 和其他基于 Chromium 的浏览器中发布的具体计划,但它已经非常接近了。
该 API 也在 Safari 99+ 中受支持,但它隐藏在实验标志后面(开发 → 实验性功能 → 高亮 API),并且接口略有不同,因为它使用 StaticRange
对象。
Firefox 尚未支持该 API,不过你可以 阅读 Mozilla 关于此 API 的立场 以获取更多信息。
演示
说到 Microsoft Edge,他们设置了一个演示,你可以在其中试用 CSS 自定义高亮 API。但在尝试演示之前,请确保你正在使用启用了 about:flags
页面中的实验性 Web 平台功能标志的 Chrome 或 Edge Canary。
/button 查看演示
该演示使用自定义高亮 API 根据你在页面顶部搜索字段中键入的内容来高亮页面中的文本范围。
页面加载后,JavaScript 代码检索页面中的所有文本节点(使用 TreeWalker),当用户在搜索字段中键入时,代码会迭代这些节点,直到找到匹配项。然后,这些匹配项用于创建 Range
对象,然后使用自定义高亮 API 对其进行高亮显示。
结语
那么,这个新的浏览器提供的突出显示 API 真的值得使用吗?当然!
首先,即使 CSS 自定义高亮 API 乍一看可能有点复杂(例如,必须创建范围、然后高亮、然后注册它们,最后设置样式),它仍然比创建新的 DOM 元素并将其插入正确的位置简单得多。
更重要的是,浏览器引擎可以非常非常快地为这些范围设置样式。
仅允许使用 ::highlight()
伪元素使用一部分 CSS 属性的原因是,该子集仅包含浏览器可以非常有效地应用的属性,而无需重新创建页面的布局。通过在页面周围插入新的 DOM 元素来突出显示文本范围需要引擎做更多工作。
但不要相信我的一面之词。参与 API 开发的 Fernando Fiori 创建了一个很好的 性能比较演示。在我的电脑上,CSS 自定义高亮 API 的性能平均比基于 DOM 的高亮快 5 倍。
随着 Chromium 和 Safari 实验性支持的到来,我们正在接近可以投入生产使用的阶段。我迫不及待地想看到浏览器一致地支持自定义高亮 API,并看看它将解锁哪些功能!
迫切期待这个功能!CSS 自定义属性是否也支持高亮名称,以便我们能够动态设置样式(如你提到的协作示例)?
目前规范不允许使用自定义属性作为 CSS :highlight() 伪元素中的名称。但是可以通过切换注册 Highlight 对象的名称来实现动态样式。
对这个功能感到非常兴奋!我也在我的个人项目中遇到了文本高亮的问题。希望这个新的 API 很快就能在所有浏览器中得到支持✨
我很高兴尝试跨节点边界的这些高亮的使用。我注意到 Microsoft 的演示仅在要高亮的文本位于单个节点内时才有效。
有谁知道这是否适用于高亮 \<input> 或 \<textarea> 中的文本?
@Darren 它似乎不适用于 textarea 和 input。