搜索结果高亮我以前用 span 包裹算坐标,今天 CSS 自己会了——::highlight() 把文字选中区域样式彻底原生化了

搜索结果高亮我以前用 span 包裹算坐标,今天 CSS 自己会了——::highlight() 把文字选中区域样式彻底原生化了

做搜索功能的时候,把用户输入的关键词在结果页高亮出来,这个需求几乎每个项目都会遇到。大多数人的做法是在渲染的时候给匹配文字外面包一层 span,再手动加背景色。一旦结果要动态更新,或者搜索词在页面多个地方出现,这个方案就开始变得麻烦——你要维护一套插入/删除 span 的逻辑,DOM 结构被污染,有时候还会遇到嵌套问题。

Chrome 126 之后,这件事有了一个原生的解决方案。

CSS Custom Highlight API 是一套浏览器内置的文本高亮机制,核心思路是:高亮样式和 DOM 结构彻底分开。你不需要改动任何 HTML,只需要告诉浏览器「哪段文字的哪些位置要显示什么样式」,浏览器自己渲染。

整个流程分四步:

第一步:创建 Range 对象。

Range 是浏览器原生用来描述「一段文本从哪到哪」的结构,你可以通过字符偏移量精确描述任意文字区间:

const textNode = document.querySelector('p').firstChild;
const range = new Range();
range.setStart(textNode, 5);  // 从第5个字符开始
range.setEnd(textNode, 12);   // 到第12个字符结束

第二步:创建 Highlight 对象。

一个 Highlight 可以容纳多个 Range,也就是说你可以把散落在页面各处的文字归到同一个高亮组里:

const highlight = new Highlight(range);

第三步:注册到 HighlightRegistry。

HighlightRegistry 通过 CSS.highlights 这个全局 Map 来管理,key 是你自己定义的名字:

CSS.highlights.set('search-match', highlight);

第四步:用 ::highlight() 伪元素写样式。

这步和 ::selection 的用法很像,只不过这次高亮范围是你自己定的:

::highlight(search-match) {
  background-color: #ff0066;
  color: white;
  text-decoration: underline;
}

效果是即时的——Range 改了,样式自动更新,不需要重新操作 DOM。

这个 API 的实际价值不在于「写起来更短」,而在于「不改 DOM」。以前包 span 的方案有个副作用:你在代码里用 innerText 或者 textContent 取文字,会把那些用来高亮的标签也算进去;你用 CSS selector 选元素,那些 span 会干扰你的布局逻辑。Custom Highlight API 完全绕开了这个问题,文字还是原来的文字,只是渲染层多了一层样式覆盖。

另一个典型场景是协同编辑。多人同时编辑一份文档时,每个人选中的文字需要有不同的颜色标识。传统方案要给每个用户维护一套插入 colored span 的逻辑,现在只需要各自创建自己的 Highlight 注册进去,样式完全不冲突:

const user1Highlight = new Highlight(user1Range);
const user2Highlight = new Highlight(user2Range);
CSS.highlights.set('user-1', user1Highlight);
CSS.highlights.set('user-2', user2Highlight);

关于浏览器支持,这个 API 在 Baseline 2025(2025 年 6 月之后),Chrome 126+、Edge 126+ 支持,Firefox 和 Safari 还在跟进中,生产环境使用建议做 @supports 检测:

@supports (selector(::highlight(foo))) {
  ::highlight(search-match) {
    background-color: #ffdd00;
  }
}

最后要注意的是 ::highlight() 只支持有限的几个样式属性:color、background-color、text-decoration、text-shadow、-webkit-text-stroke、text-fill 颜色,其他属性会忽略。这是渲染层的限制,不是 bug,不要浪费时间试 border 或者 padding 这类属性。

下一步:如果你的项目现在用 span 包裹的方式做搜索高亮,可以考虑迁移到这个 API 上。具体做法是:把「匹配文字的字符偏移范围」存下来而不是把 span 存下来,Range 对象是纯数据,好管理得多。

评论区

0 条评论

登录后可评论。

小鹿·界面实验室 354 阅读