必须显式声明background-color、color、padding三项css,并在深色模式下适配;禁用innerhtml.replace(),改用treewalker遍历文本节点并双重转义;具备语义与可访问性优势,不可简单替换为。

直接用 <mark></mark> 就能高亮?先看默认样式靠不靠谱
浏览器确实会给 <mark></mark> 加黄色背景,但这个“开箱即用”只在本地开发时成立。Safari 阅读模式会剥离 UA 样式,Outlook 邮件客户端完全忽略它,Windows 高对比度设置还会强制重置 background-color。别指望用户看到和你一样的效果。
必须显式声明三项 CSS:background-color、color、padding。例如:
mark {
background-color: #ffeb3b;
color: #212121;
padding: 0.1em 0.25em;
}
深色模式下要手动适配:@media (prefers-color-scheme: dark) 里重设颜色,否则黄底黑字在暗色主题下对比度可能低于 WCAG AA 标准(≥4.5:1)。
为什么不能用 innerHTML.replace() 做搜索高亮
这是最常见也最危险的写法:把整个容器的 innerHTML 当字符串处理。它同时打开三扇门——XSS、DOM 结构破坏、HTML 解析中断。
- 用户搜
<script>alert(1)</script>→ 拼进字符串后脚本执行 - 用户搜
cat[0]→new RegExp('cat[0]')报错,脚本中断 - 原文是
<code>console.log()→ 替换后变成<code>console.<mark>log</mark>(),<mark></mark>被撕开,DOM 解析失败 - 原文是
<img src="logo.png">→ 变成<img src="<mark>logo</mark>.png">,图片挂掉
根本问题在于:没遍历文本节点,而是把 HTML 字符串当纯文本暴力替换。
安全高亮必须只操作文本节点:TreeWalker + 双重转义
核心路径是绕过字符串拼接,只在纯文本内容上做切分与替换:遍历 → 匹配 → 创建 <mark></mark> → 插入 DOM。
关键步骤:
- 用
document.createTreeWalker(element, NodeFilter.SHOW_TEXT)获取所有文本节点 - 跳过空节点:
node.textContent.trim() === '' - 对关键词双重转义:
— HTML 转义:keyword.replace(/[&"'`=/]/g, c => `${c.charCodeAt(0)};`)
— 正则转义:keyword.replace(/[.*+?^${}()|[]\]/g, '\$&') - 构造正则时加
gi标志:new RegExp(`(${escaped})`, 'gi') - 匹配后创建元素:
const mark = document.createElement('mark');→mark.textContent = match;→node.replaceWith(mark);
<mark></mark> 不是 <span class="highlight"></span> 的替代品
<mark></mark> 是唯一被读屏器识别为 “marked text” 的原生标签,搜索引擎也据此理解上下文相关性;而 <span class="highlight"></span> 只是视觉装饰,辅助技术完全无视。
误用后果:
- 屏幕阅读器不会提示“此处为搜索命中”,用户无法感知高亮存在
- SEO 丢失语义信号,搜索结果页中关键词关联性不被识别
- IE9+ 和所有现代浏览器都支持
<mark></mark>,无需 polyfill;但若项目需兼容 IE8,就得退回到<span role="mark"></span>,实际收益远低于维护成本
真正容易被忽略的是:动态插入大量 <mark></mark> 后又频繁移除,会触发 DOM 重排,页面卡顿明显——尤其在低配设备上。高亮后滚动定位也别对每个 <mark></mark> 都调用 scrollIntoView(),只处理第一个可见项并加防抖更稳妥。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











