必须显式设置 mark { background-color: #ffeb3b; color: #212121; },否则暗色模式、高对比度、打印等场景下默认黄色失效;正则替换需转义元字符、加 i 标志、遍历 Text 节点;querySelector('mark') 失败常因动态插入未重排、被覆盖、Shadow DOM 或 contenteditable 剥离;Custom Highlight API 更轻量但兼容性有限。

直接用 <mark></mark> 包裹关键词即可,但必须配合 CSS 显式设置 background-color 和 color,否则在暗色模式、打印或高对比度环境下可能完全不可读。
为什么 <mark> 不能只靠默认黄色
浏览器虽给 <mark> 设了默认背景色(通常是 #ff0),但这个值不参与 CSS cascade,且在以下场景会失效:
- 用户启用系统级暗色模式(Chrome/Firefox 会自动切换为深黄或灰黄,部分版本甚至变为透明)
- 页面启用了
@media (prefers-contrast: high),默认背景会被强制重置 - 打印样式表中未覆盖时,
<mark>可能渲染为纯黑底白字或直接消失 - 某些辅助技术(如 Windows 高对比度模式)会忽略未显式声明的
background-color
所以必须写:
mark { background-color: #ffeb3b; color: #212121; padding: 1px 4px; }
搜索关键词高亮时,正则替换容易崩在哪几个点
动态插入 <mark> 最常见的错误不是语法,而是正则处理不当导致 DOM 损坏或 XSS 风险:
-
keyword.replace(/search/g, '<mark>$</mark>')—— 若search含.、+、[等元字符,正则直接报错;必须先转义:keyword.replace(/[.*+?^${}()|[]\]/g, '\$&') - 没加
i标志:用户搜 “React”,页面里是 “react” 就匹配不上;应使用new RegExp(pattern, 'gi') - 直接对整个
innerHTML替换:若原文含<strong>Vue</strong>,正则可能切开标签,生成无效 HTML;应遍历Text节点(用TreeWalker或NodeIterator)再操作nodeValue
document.querySelector('mark') 找不到?不是大小写问题
返回 null 的真实原因往往和标签名大小写无关(HTML 不区分大小写,querySelector 也支持大小写混合):
-
<mark>是 JS 动态插入的,但插入后没触发 layout(比如在requestIdleCallback里执行,DOM 已更新但尚未重排) - 插入后又被其他逻辑清空了,例如某个 cleanup 函数调用了
element.innerHTML = ...,把刚加的<mark>覆盖掉了 - 目标元素在 Shadow DOM 内,而
querySelector默认查不到;需用shadowRoot.querySelector('mark') - 页面用了
contenteditable,某些浏览器会把<mark>当作“格式化容器”自动剥离(尤其在 Safari 中)
比 <mark> 更轻量的替代方案:CSS Custom Highlight API
如果你只是想视觉高亮、不改 DOM、还要支持多人协作标注或编辑器内实时搜索,Custom Highlight API 是目前唯一无侵入方案:
- 不污染原始 HTML,所有高亮由 JS 控制范围、CSS 控制样式,DOM 结构零修改
- 性能友好:大量高亮也不会引发重排,因为不创建新节点
- 兼容性注意:Chrome/Edge ≥105 原生支持;Firefox 需手动开启
layout.css.highlight-api.enabled;Safari 完全不支持(截至 2026 年 4 月) - 基础用法:
const range = document.createRange();<br>range.selectNodeContents(targetElement);<br>const highlight = new Highlight(range);<br>CSS.highlights.set('search-highlight', highlight);再配 CSS:::highlight(search-highlight) { background-color: #ffeb3b; color: #212121; }
真正难的是边界处理——比如跨行、跨 <span>、含换行符的文本节点,这些地方 Range 构造稍有偏差,高亮就会偏移或截断。别图省事用 innerText 算位置,老老实实用 getClientRects() 或 Range.getBoundingClientRect() 校验。

















