CSS Custom Highlight API 的机制与实践
在传统方案里,搜索命中、批注、语法标注通常通过 <span> 或 <mark> 包裹文本完成。该方案虽然直观,但会引入 DOM 污染、复制选择行为变化、以及样式冲突扩散等问题。
使用 CSS Custom Highlight API 用来给文本范围添加样式化标注,可以有效避免上述问题。它允许我们在不修改 DOM 结构的前提下,直接在渲染层实现高亮效果。
先看一个可交互示例:
Matches: 0
This paragraph demonstrates text highlighting. The highlight is not created by inserting span elements, but by applying Range objects at render time.
When you type "highlight" or "range", the page marks all matches in real time while keeping the original DOM structure unchanged.
This approach works especially well for code readers, document search, and annotation systems.
通过上面的输入框,你可以动态输入关键词,看看它是如何高亮文本的。这个示例的核心实现其实非常简单,主要步骤如下:
- 用 JavaScript 构造一组
Range。 - 将这组
Range交给new Highlight(...ranges)。 - 通过
CSS.highlights.set(name, highlight)注册命名高亮。 - 使用
::highlight(name)定义样式。
对应代码如下:
const range = new Range()
range.setStart(textNode, 0)
range.setEnd(textNode, 4)
const highlight = new Highlight(range)
CSS.highlights.set('search-hit', highlight)::highlight(search-hit) {
background-color: #ffe58f;
color: #1f1f1f;
}和 <mark> 或 <span> 相比,它的本质优势在于“表示层分离”:你不需要为了视觉效果去修改文档语义结构。
为什么这件事在工程上更优
如果把“文本高亮”视为一个检索与标注问题,主要复杂度在匹配策略,不在渲染输出。
- 朴素版本:遍历文本节点 +
indexOf,实现简单,足够覆盖大多数场景。 - 中大型文档:可以增量更新范围,减少重复扫描。
- 多来源高亮:搜索命中、评论批注、语法提示可以拆成不同命名高亮,互不干扰。
也就是说,这个 API 让我们把注意力放回“匹配与策略”本身,而不是和 DOM 包装节点反复纠缠。
边界与兼容性注意点
这套 API 并非“无条件通吃”,实际落地时需要注意三件事:
::highlight()只支持一部分 CSS 属性,不是所有样式都能生效。- 高亮范围可能重叠,必要时可通过
Highlight.priority设计层级策略。 - 需要做特性检测:
'highlights' in CSS,并准备降级方案(例如回退到<mark>)。
一个实用的检测分支:
if (!('highlights' in CSS) || !('Highlight' in window)) {
// 降级策略:例如 mark/span 或不启用高亮
}小结
CSS Custom Highlight API 的收益很明确:
- DOM 更干净,语义层不被高亮逻辑绑架。
- 文本复制、选择、后续维护更稳定。
- 高亮能力可组合,适合继续扩展到批注和编辑器类场景。
对于搜索命中、阅读批注、编辑器提示等场景,这套 API 都值得优先评估。