mark是唯一语义正确、可访问且被搜索引擎识别的高亮方式,但必须显式声明background-color和color等CSS样式,转义正则元字符与HTML特殊字符,仅操作文本节点而非innerHTML,按关键词长度降序处理以防嵌套,并适配暗色模式与高对比度环境。

mark 是唯一语义正确、可访问且被搜索引擎识别的高亮方式,但直接写 关键词 不够,必须显式设置 CSS 样式、转义正则元字符、只操作文本节点,否则在暗色模式、打印、Safari 阅读模式或高对比度环境下会完全不可见,甚至触发 XSS。
为什么mark必须写死background-color和color
浏览器虽给 mark 设了默认背景(如 #ff0),但它不参与 CSS cascade,在以下场景会失效:
- 系统启用暗色模式时,Chrome/Firefox 可能将背景重置为透明或灰黄
-
@media (prefers-contrast: high)下,未声明的background-color被强制清空 - 打印样式表中未覆盖时,
mark渲染为黑底白字或直接消失 - Windows 高对比度模式下,辅助技术忽略未显式声明的背景色
必须至少写:
mark {
background-color: #ffeb3b;
color: #212121;
padding: 0.1em 0.25em;
}
若支持暗色主题,补上:
@media (prefers-color-scheme: dark) {
mark {
background-color: #ffca28;
color: #121212;
}
}
动态插入mark时,innerHTML.replace()为什么最危险
对整个容器的 innerHTML 做字符串替换,是线上白屏、XSS 和 DOM 解析中断的头号原因:
- 用户搜
<script>alert(1)</script>→ 拼进字符串后直接执行脚本 - 关键词含
&、"、→ HTML 结构错乱,如被切开price:
- 搜
[a-z]这类正则元字符 →new RegExp(keyword)抛SyntaxError - 在
中匹配
logo→ 变成,图片挂掉
安全做法是绕过字符串拼接,只操作文本节点:
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT)遍历所有纯文本节点 - 对每个
node.textContent执行匹配,跳过空值或仅空白符节点 - 先转义关键词:
keyword.replace(/[.*+?^${}()|[]]/g, '\$&') - 用
new RegExp(`(${escaped})`, 'gi')匹配,捕获组保留原始大小写 - 创建
document.createElement('mark'),设textContent = matchedText,再用node.parentNode.replaceChild(mark, node)
querySelector('mark') 返回 null 的真实原因
不是大小写问题(HTML 不区分大小写),常见真实原因是:
-
是 JS 动态插入的,但插入后没触发 layout(例如在requestIdleCallback或异步回调里执行) - 插入后又被其他逻辑清空,比如某个 cleanup 函数执行了
element.innerHTML = '' - 目标元素在 Shadow DOM 内 → 需用
shadowRoot.querySelector('mark') - 页面用了
contenteditable,Safari 会自动剥离(尤其在编辑后回车或聚焦时)
多个关键词高亮时,为什么“长词优先”是硬规则
比如同时高亮 "JavaScript" 和 "Script",如果先匹配短词,可能产生嵌套非法结构:
- 错误顺序:
"Script"先被包裹 →JavaScript - 再匹配
"JavaScript"→JavaScript - 浏览器会自动修正为两个独立
,但语义断裂、样式错位、getBoundingClientRect()定位偏移
正确做法:
- 按关键词长度降序排序:
keywords.sort((a, b) => b.length - a.length) - 每次高亮前,先清除已有
(避免重复包裹) - 对每个词单独遍历文本节点,不复用已修改的 DOM 树
复杂点不在怎么标,而在怎么不标错——文本节点边界、正则转义、DOM 插入时机、多词顺序,任意一环松动,高亮就从辅助功能变成可访问性灾难。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











