CSS 自定义高亮 API:网页文本范围高亮技术的未来

Avatar of Patrick Brosset
Patrick Brosset 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

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

Animation screenshot of the CSS Custom Highlight API demo.

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

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

浏览器本身通常会处理这些样式情况。可编辑区域(如<textarea>)会自动获得拼写波浪线。查找命令会自动突出显示找到的文本。

但是,当我们想自己进行这种样式设置时该怎么办?在网络上执行此操作一直是一个常见问题。它可能让很多人花费了比应该花费更多的时间。

这不是一个简单的解决问题。我们不仅仅是用一个带有类的<span>来包装文本并应用一些 CSS。实际上,这需要能够正确突出显示任意复杂 DOM 树中的多个文本范围,并可能跨越 DOM 元素的边界。

有两个常见的解决方案,包括

  1. 设置文本范围伪元素的样式,以及
  2. 创建您自己的文本高亮系统。

我们将首先回顾它们,然后看看即将推出的能够改变这一切的CSS 自定义高亮 API。但是,如果您

潜在解决方案 #1:可设置样式的文本范围

可能最著名的可设置样式的文本范围是用户选择。当您使用指向设备在网页中选择一段文本时,会自动创建一个Selection对象。实际上,现在尝试在此页面上选择文本,然后在 DevTools 控制台中运行document.getSelection()。您应该会看到有关所选文本的位置信息。

DevTools window showing the position of the current selection in the console.

事实证明,您也可以通过 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 元素的文本时。

Illustration showing a line of HTML with an emphasis element and a strong element with a bright yellow highlight running through them.

有趣的是,CodeMirrorMonaco(为 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);

值得注意的是,如果作为第一个参数传递的节点是文本节点还是其他节点,则setStartsetEnd方法的工作方式有所不同。对于文本节点,偏移量对应于节点中的字符数。对于其他节点,偏移量对应于父节点中的子节点数。

同样值得注意的是,setStartsetEnd不是描述范围开始和结束位置的唯一方法。查看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() 伪元素一起使用。

更新高亮

有多种方法可以更新页面上的高亮文本。

例如,你可以使用 CSS.highlights.clear() 完全清除高亮注册表,然后从头开始。或者,你也可以更新底层范围,而无需重新创建任何对象。为此,再次使用 range.setStartrange.setEnd 方法(或任何其他 Range 方法),浏览器将重新绘制高亮。

但是,Highlight 对象的工作方式类似于 JavaScript Set,这意味着你还可以使用 highlight.add(newRange) 将新的 Range 对象添加到现有的 Highlight 中,或者使用 highlight.delete(existingRange) 删除 Range

第三,你还可以从 CSS.highlights 注册表中添加或删除特定的 Highlight 对象。由于此 API 的工作方式类似于 JavaScript Map,因此你可以setdelete 来更新当前注册的 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,并看看它将解锁哪些功能!