标签必须配合显式样式、文本节点遍历和关键词转义才能稳定生效;直接使用会在高对比度模式、outlook邮件、css重置下失效,且需处理xss、dom撕裂、嵌套、多关键词排序、unicode匹配及深色模式适配。

标签不是“加个黄底”的快捷方式,它必须配合显式样式、文本节点遍历和关键词转义才能在真实项目中稳定生效。
直接写 <mark></mark> 会失效的三种典型场景
浏览器默认黄色背景在以下情况会完全不可见:Windows 高对比度模式下被强制重置为纯黑;Outlook 邮件客户端忽略所有 UA 样式;CSS 重置规则(如 * { background: transparent })清空了默认背景。不写 CSS 就上线,等于放弃可访问性。
常见错误现象包括:mark 在深色主题中变成黑底黑字、搜索结果页对盲人用户“静默无提示”、服务端渲染后前端 JS 二次包裹导致嵌套 <mark><mark>xxx</mark></mark>。
- 必须同时声明
background-color和color,否则在禁用 UA 样式的环境里文字可能消失 - 避免只写
background: none—— 它不会清空background-image,应写background-color: transparent - 加
padding和border-radius是刚需,原生mark紧贴文字,边缘生硬易误读
动态高亮关键词必须遍历文本节点,不能碰 innerHTML
直接 el.innerHTML = el.innerHTML.replace(...) 会在三处翻车:用户搜 [a-z] 不转义正则元字符 → 报错中断;搜 <script>alert(1)</script> → XSS;原文含 <code>console.log() → 替换后撕开标签,DOM 解析失败。
正确路径是只操作纯文本节点(node.nodeType === 3),跳过所有元素节点:
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT)获取全部文本节点 - 对每个节点调用
textContent.replace(),但匹配前必须双重转义:keyword.replace(/[&"'`=/]/g, c => `${c.charCodeAt(0)};`)(HTML 转义)+keyword.replace(/[.*+?^${}()|[]]/g, '\$&')(正则转义) - 匹配后创建新节点:
const mark = document.createElement('mark'); mark.textContent = match;,再用node.parentNode.replaceChild(mark, node)
多关键词高亮要按长度降序处理,否则语义断裂
同时高亮 "JavaScript" 和 "Script" 时,若先匹配短词,会导致 <mark>Java<mark>Script</mark></mark> 这类非法嵌套。浏览器虽会自动修正为两个独立 mark,但语义已断裂、CSS 样式错位、读屏器播报混乱。
解决方案很简单:把关键词数组按字符串长度从长到短排序后再遍历匹配。
- 排序逻辑:
keywords.sort((a, b) => b.length - a.length) - 中文场景额外注意:正则需加
u标志支持 Unicode,避免全角/半角混搜时漏匹配 - 大小写不敏感匹配后,用捕获组保留原文本大小写:
new RegExp(`(${escaped})`, 'gi')→ 替换为'<mark>$1</mark>'
深色模式与可访问性不能靠“默认值”赌运气
仅写基础样式不够,mark 在暗色主题、高对比度设置、阅读模式下表现差异极大。必须手动适配:
- 至少声明四条:
background-color、color、padding、border-radius - 深色模式补媒体查询:
@media (prefers-color-scheme: dark) { mark { background-color: #ffca28; color: #121212; } } - 别只靠颜色区分:色觉障碍用户可能无法识别黄底,建议加
box-shadow: inset 0 -2px 0 #007acc或微粗(但需测试行高是否溢出)
真正难的不是让字变黄,而是让高亮在 XSS 防御、DOM 安全、语义传达、可访问性、暗色适配这五层约束下稳定生效——漏掉任何一层,都可能让搜索页对盲人用户“不可见”,或在邮件里“完全透明”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











