你以为高亮只能靠塞 span?今天 CSS 把这件事彻底原生化了
做过搜索高亮的都知道这个流程:用户一打字,你就得 innerHTML.replace() 把匹配文字包进 <mark>,DOM 一变 event listener 全飞,跨标签的匹配直接抛异常,清理不干净还留一堆脏节点。CSS Custom Highlight API 把这套彻底推倒重来——浏览器自己会画高亮,你的 DOM 纹丝不动。
四行代码,DOM 零改动
传统方案:
// 每输一个字就要重建一次 DOM
el.innerHTML = el.innerHTML.replace(
new RegExp(term, 'gi'),
'<mark>$&</mark>'
);
新方案:
// 1. 创建 Range
const range = new Range();
range.setStart(textNode, startOffset);
range.setEnd(textNode, endOffset);
// 2. 包装成 Highlight
const highlight = new Highlight(range);
// 3. 注册到全局注册表
CSS.highlights.set('search', highlight);
// 4. CSS 里声明样式
::highlight(search) {
background: oklch(88% 0.17 95);
color: #000;
}
没有任何 innerHTML 操作,DOM 里的文字还是原来的文字——浏览器在渲染层”贴”了一层荧光笔,坐标精确到字符级。
为什么比塞 span 快五倍
传统 span 方案每次都要:解析 HTML → 创建新节点 → 插入 DOM → 触发 layout recalc → 重绘。
Custom Highlight API 跳过了 layout 阶段。utilitybend.com 的深度分析把这个过程拆得很清楚:
- Layout 阶段完全跳过:文字块的几何信息早就缓存好了,浏览器知道每个字符在屏幕上的坐标。
- Paint 阶段拦截:渲染引擎在画每一个字的时候,顺手查一下 Highlight 注册表,有没有覆盖到这个坐标。
- Overlay painting:命中高亮范围就直接在字下面画一个背景矩形,不需要创建任何节点,不影响 Accessibility Tree。
Fernando Fiori(Chrome 团队)做的性能对比 demo 实测:新方案比 DOM 方案平均快 5 倍。CSDN 的一篇实战文章更极端——3000+ 高亮批注场景,span 方案帧率掉到 8fps,Highlight API 稳在 60fps。
三个真实场景
搜索高亮是最直接的应用,但这个 API 的价值远不止于此。
协同编辑:每个用户一个 Highlight 对象,名字不同颜色不同,用户 A 画的黄色和用户 B 画的绿色重叠时靠 highlight.priority 决定谁压谁。DOM 结构完全不受影响,用户断开重连只需要清空 CSS.highlights.delete('user-a')。
语法高亮:不用给每个 token 包 span,Range 直接指向源码里的字符区间,多层高亮(关键字一种色、字符串一种色、注释一种色)并行叠加,::highlight(hl-keyword)、::highlight(hl-string) 各司其职。
拼写检查:::highlight(spell-error) + text-decoration: underline wavy red,效果和浏览器原生的 ::spelling-error 完全一致,但范围完全由你控制。
OpaqueRange:表单里的光标也能画高亮
Chrome 153+ 引入了 OpaqueRange,这是 Highlight API 的扩展——它专门处理 <input> 和 <textarea> 内部的文字。传统 Range 只能指向普通 DOM 节点,OpaqueRange 可以指向表单控件的 value 属性,配合 getBoundingClientRect() 就能实现光标弹出 emoji picker、搜索词实时高亮等交互。
const range = new OpaqueRange();
range.setStart(inputElement, 4);
range.setEnd(inputElement, 10);
CSS.highlights.set('input-highlight', new Highlight(range));
兼容性:93% 全球覆盖,Safari 需开 flag
- Chrome 105+ ✅(2022年9月)
- Firefox 140+ ✅(2025年6月,Baseline 2025)
- Safari 17.2+ ✅(需开启实验功能)
- 全球覆盖率:93%
唯一需要注意的是 ::highlight() 只支持影响文本渲染的属性:background-color、color、text-decoration 及其关联属性、text-shadow、-webkit-text-stroke-*。想改 margin、padding、display?浏览器直接忽略,不会报错。
如果你的用户里有 Safari 忠实粉(尤其是移动端),记得加个 feature detection:
if (!CSS.highlights) {
// 降级:回退到传统的 span 方案
}
下一步
现在你手里有了四块拼图:Range 定义范围、Highlight 打包、Registry 注册、CSS 声明样式。下次做搜索、标注、协作编辑,先问自己一个问题——能不能不动 DOM?
能的话,这四行代码比任何高亮库都轻。
参考来源:
- MDN CSS Custom Highlight API 文档(developer.mozilla.org)
- CSS-Tricks: The Future of Highlighting Text Ranges(2022)
- utilitybend.com: Under My Radar — The Highlight API(2026.07)
- modern-css.com: Text Highlighting Without DOM Manipulation(2026.04)
- CSDN: CSS Custom Highlight API 实战(2026.07)
- Microsoft Edge OpaqueRange Demo(microsoftedge.github.io)
评论区
登录后可评论。