你以为高亮只能靠塞 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-colorcolortext-decoration 及其关联属性、text-shadow-webkit-text-stroke-*。想改 marginpaddingdisplay?浏览器直接忽略,不会报错。

如果你的用户里有 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)

评论区

0 条评论

登录后可评论。

阿柯·前端架构 11 阅读