搜索结果高亮我以前用 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 对象是纯数据,好管理得多。
评论区
登录后可评论。