“mark”标签需通过文本节点遍历、关键词双重转义、显式样式声明及多关键词降序匹配四步实现安全高亮;直接操作innerhtml易破坏dom、引发xss,且忽略可访问性与主题适配。

mark 标签本身不参与搜索逻辑,它只是语义容器;所谓“高亮渲染架构”,本质是三件事的协同:文本定位、安全包裹、样式落地。没这三层,mark 就只是个空壳。
只操作文本节点,不碰 innerHTML
直接对容器元素调用 innerHTML.replace() 是最常见也最危险的起点。它会把 <img src="react.png"> 里的 react 也匹配进去,结果变成 <img src="<mark>react</mark>.png">,图片直接挂掉。
正确做法是用 document.createTreeWalker(element, NodeFilter.SHOW_TEXT) 遍历所有文本节点,跳过空节点和仅含空白符的节点。每个 textNode.textContent 才是唯一合法的匹配目标。
- 避免 DOM 结构破坏:不解析 HTML 字符串,只处理纯文本内容
- 保留事件绑定:父容器上的 click、hover 等监听器不会因替换而丢失
- 绕过 XSS 风险:不拼接用户输入到字符串再赋给
innerHTML
关键词必须双重转义
用户输入的搜索词既是 HTML 内容,又是正则模式——它要进 textContent,也要进 new RegExp()。不转义,一个 . 就让正则崩溃,一个 & 就让后续标签错位。
先做 HTML 实体转义(防注入):keyword.replace(/&/g, '&').replace(/, '/g, '>');再做正则元字符转义(防匹配失败):keyword.replace(/[.*+?^${}()|[\]]/g, '\$&')。
- 顺序不能反:先 HTML 转义,再正则转义;否则
&里的&可能被二次误处理 - 中文关键词一般不用
\b单词边界,但要注意全角空格、标点是否参与匹配 - 大小写处理统一用
gi标志,别在正则里混用toUpperCase()做比对
样式必须显式声明,且区分语义场景
浏览器默认的 mark 黄色背景在 Safari 阅读模式、Windows 高对比度、深色主题、邮件客户端中基本不可见。更麻烦的是,normalize.css 或重置样式常把 mark 的 padding 清零,导致高亮文字紧贴边缘。
必须写死关键样式:mark { background-color: #4285f4; color: #fff; padding: 0.1em 0.25em; border-radius: 2px; }。如果支持多关键词,就用类名区分:<mark class="mark-search"></mark>、<mark class="mark-suggestion"></mark>。
- 禁用
background简写,它可能意外带上 UA 默认的line-height或padding - 深色模式下需配
@media (prefers-color-scheme: dark)规则,不能只靠系统自动适配 - 别全局重写
mark,否则搜索高亮、代码 diff、引用标注全挤在一个样式里,后期无法解耦
多关键词匹配要按长度降序执行
用户搜 ["script", "JavaScript"],如果先匹配短词 script,可能把 JavaScript 拆成 Java<mark>script</mark>,语义断裂,且后续无法再匹配完整词。
解决方法是先对关键词数组按 keyword.length 降序排序,再逐个执行高亮逻辑。每次匹配后,把已包裹的文本节点从遍历路径中剔除(或标记已处理),避免重复嵌套。
- 嵌套
<mark><mark>xxx</mark></mark>是非法 HTML,浏览器会自动拆开,读屏器播报混乱 - 若需支持模糊匹配(如拼音首字母、同义词),应在匹配前做预处理,不在
mark插入阶段引入额外逻辑 - 服务端已返回带
mark的 HTML 时,前端 JS 必须先清理旧标记:element.querySelectorAll('mark').forEach(el => el.replaceWith(el.textContent))
真正难的不是怎么加 mark,而是怎么让它在各种设备、辅助技术、主题模式、输入组合下都稳定传达“这就是你搜的词”——这要求每一步都绕过捷径,直面 DOM、正则、CSS 和可访问性的具体约束。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











