标签不能直接使用,因其在高对比度模式、Outlook邮件、CSS重置后会失效;动态Markdown代码块需先转义并生成标准HTML结构再调用hljs.highlightAll();搜索高亮必须遍历文本节点并转义正则元字符与HTML特殊字符,避免DOM撕裂和XSS。

直接用 <mark></mark> 标签不行,浏览器默认样式在高对比度模式、Outlook 邮件、CSS 重置后会完全失效;动态插入的 Markdown 代码块(如 ```js)也不会被 hljs.highlightAll() 自动识别;搜索关键词高亮若用 innerHTML.replace(),轻则 DOM 撕裂,重则 XSS。
为什么 <mark></mark> 直接写不生效
它不是“加个黄底”的快捷方式,而是语义化标记。三大失效场景必须提前防住:
- Windows 高对比度模式下,系统强制清空所有背景色,
<mark></mark>变成黑底黑字甚至不可见 - Outlook 等邮件客户端忽略 UA 样式,
<mark></mark>渲染为纯文本 - 项目用了
* { background: transparent }这类重置规则,<mark></mark>的默认黄色直接被抹掉
必须显式声明 background-color 和 color,且建议加 padding 和 border-radius 提升可读性。例如:<mark style="background-color: #fff9c4; color: #1f2329; padding: 0.1em 0.3em; border-radius: 3px;">高亮文本</mark>
动态搜索高亮必须遍历文本节点
el.innerHTML = el.innerHTML.replace(...) 是典型翻车操作,原因有三:
立即学习“前端免费学习笔记(深入)”;
实现 Markdown↔ HTML 互转,支持导出 PDF,用于用户请求的文档格式转换。
- 用户搜
a.b?c,正则未转义元字符 → 报错中断 - 用户搜
<script>alert(1)</script>→ XSS 注入 - 原文含
<code>console.log()→ 替换后变成<code>console.<mark>log</mark>(),DOM 解析失败
正确做法是只操作纯文本节点:
function highlightText(element, keyword) {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
const escapedKeyword = keyword
.replace(/[.*+?^${}()|[]\]/g, '\$&') // 正则转义
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
while ((node = walker.nextNode())) {
const text = node.textContent;
const regex = new RegExp(`(${escapedKeyword})`, 'gi');
if (regex.test(text)) {
const span = document.createElement('span');
span.className = 'highlight';
span.innerHTML = text.replace(regex, '<mark>$1</mark>');
node.parentNode.replaceChild(span, node);
}
}
}
Markdown 代码块动态高亮要先转结构再调用 hljs
hljs.highlightAll() 只扫描已存在的 <code><pre class="brush:php;toolbar:false;"></pre>,对流式插入的
<pre class='brush:js;toolbar:false;'> console.log(1)文本无感知。
关键两步缺一不可:
- 用正则提取
...
并转为标准 HTML 结构,同时对代码内容做 HTML 实体转义(&→&,→ <code> 等) - 插入 DOM 后,立即调用
hljs.highlightElement(codeEl)(推荐),而非等整个容器再highlightAll()
示例转换函数:
<pre class="brush:php;toolbar:false;">function convertMarkdownCodeBlocks(htmlString) {
return htmlString.replace(/```(w+)?
([sS]*?)
```/g, (match, lang, code) => {
const language = lang || 'plaintext';
const escapedCode = code
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
return `<pre class='brush:language-${language};toolbar:false;'>${escapedCode}`;
});
}
最常被忽略的是:高亮逻辑必须和内容插入严格同步——不能等 setTimeout 或依赖 MutationObserver 延迟触发,否则用户看到的是未高亮的原始代码块,体验断层。尤其在 SSE 流式响应中,每 chunk 插入后都得立刻处理并高亮。


















