<mark>是唯一语义正确、可访问且被读屏器和搜索引擎识别的关键词高亮方式,必须通过DOM操作文本节点实现,配合转义、样式覆盖与安全处理,否则在暗色模式、高对比度、邮件客户端或Safari阅读模式下会失效甚至触发XSS。

mark 是唯一语义正确、可访问且被读屏器和搜索引擎识别的关键词高亮方式,不是视觉装饰替代品。直接写 <mark>关键词</mark> 不够,必须配合转义、样式覆盖和 DOM 安全操作,否则在暗色模式、高对比度、邮件客户端或 Safari 阅读模式下会完全不可见,甚至触发 XSS。
为什么 innerHTML.replace() 动态插入 mark 最危险
这是线上最常导致白屏、XSS 或 DOM 解析中断的写法:
- 用户搜
<script>alert(1)</script>,拼进字符串后直接执行脚本 - 关键词含
&、"、<等字符,导致 HTML 结构错乱(如<p>price: 被切开) - 搜
[a-z]这类正则元字符,new RegExp(keyword)直接抛SyntaxError - 在
<img src="logo.png">里匹配logo,结果变成<img src="<mark>logo</mark>.png">,图片挂掉
安全高亮必须只操作文本节点,不碰 innerHTML
核心是绕过字符串拼接,用原生 DOM 方法组装:
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false)遍历所有纯文本节点 - 对每个
node.textContent执行匹配,跳过空值或仅空白符节点 - 先转义关键词:
escapeHtml(keyword).replace(/[.*+?^${}()|[]\]/g, '\$&') - 用
new RegExp(`(${escaped})`, 'gi')匹配,捕获组还原原始大小写 - 创建
document.createElement('mark'),设textContent = matchedText,再用node.parentNode.replaceChild(mark, node)
mark 样式必须显式声明,浏览器默认值不能交付
默认黄色背景在以下场景会彻底失效或不可读:
- Windows 高对比度模式:强制重置为黑底白字,黄底消失
- Safari 阅读模式:忽略 UA 默认样式,
mark渲染为无背景普通文字 - 深色主题下:
#ff0黄底 + 黑字对比度低于 WCAG AA 标准 - Outlook 等邮件客户端:禁用全部 UA 样式,未声明即透明
至少写死这三行:
mark {
background-color: #ffeb3b;
color: #212121;
padding: 0.1em 0.25em;
}
若支持暗色主题,加媒体查询:
@media (prefers-color-scheme: dark) {
mark {
background-color: #ffca28;
color: #121212;
}
}
中文搜索高亮要注意全角/半角与边界逻辑
中文没有天然单词边界,但误匹配仍常见:
- 用户搜 “JavaScript”,页面是 “javascript” → 必须加
i标志,且注意大小写还原 - 用户搜 “C++”,未转义
+会导致正则报错 → 先调用escapeRegex() - 多关键词如 [“React”, “React Router”],必须按长度从长到短排序,否则 “React” 先匹配,把 “React Router” 切成
<mark>React</mark> Router - 避免嵌套:
<mark><mark>xxx</mark></mark>会破坏语义,插入前先querySelectorAll('mark')清理已有标记
真正难的不是加黄色背景,而是让高亮在所有辅助技术、系统偏好、渲染上下文里都稳定可读且语义准确——mark 的价值不在视觉,而在它告诉浏览器“这段文本和当前搜索强相关”,这个信号一旦丢失,高亮就只剩颜色了。

















