必须用Range+TreeWalker组合实现多行精准高亮:先用window.getSelection()获取真实选区,再用TreeWalker遍历文本节点并跳过script/style等非内容节点,对匹配文本创建mark元素并用range.surroundContents()原位包裹,同时按长度降序处理关键词、转义正则元字符、显式声明CSS样式以保障可访问性与兼容性。

用 Range + mark 实现多行文本精准高亮
直接操作 innerHTML 做字符串替换,会在换行、空格、嵌套标签处崩坏 DOM,比如把 React Native 中的 React 替换掉, 就可能被切开。真正安全的做法是走 DOM 节点遍历,用浏览器原生 Range 包裹真实选区。
关键步骤:
- 用
window.getSelection()获取用户当前选中的文本范围(支持跨行、跨标签) - 调用
range.cloneContents()或range.toString()提取纯文本内容(不含 HTML 标签) - 对每个匹配到的文本片段,创建
document.createElement('mark'),再用range.surroundContents(markEl)原位包裹 - 若需高亮多词(如搜索多个关键词),先用
TreeWalker遍历所有Node.TEXT_NODE,再对每个文本节点执行matchAll(),避免重复包裹同一段
TreeWalker 遍历文本节点比 querySelectorAll('*') 更可靠
很多脚本用 document.querySelectorAll('*').forEach(el => el.textContent.replace(...)),这会把 <script></script>、 甚至注释里的内容也一并处理,还可能把 、 对应的空白逻辑搞乱。
正确做法是限定只处理可见文本节点:
- 用
document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode: node => node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }) - 遍历时跳过
node.parentElement是script、style、textarea、pre的节点 - 对每个保留的文本节点,调用
node.textContent.matchAll(new RegExp(keyword, 'gi')),拿到所有index和length - 从后往前用
node.splitText()切分并插入mark,防止索引偏移
批量高亮多个关键词时,顺序和边界必须严格控制
如果搜索词含 "js" 和 "javascript",不加控制就会出现 javascript 被二次拆成 jscript —— 这是因为短词正则在长词内部又被匹配了一次。
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
解决方案很具体:
- 先把所有关键词按长度降序排列:
keywords.sort((a, b) => b.length - a.length) - 每个关键词做全词匹配:用
new RegExp('\b' + escapedKeyword + '\b', 'gi'),注意双反斜杠转义 - escapeHtml 处理关键词本身:把
.、?、[等正则元字符转义,否则user.name会被当成user[any]name - 高亮完一个词后,不再对已包裹的
mark内部做二次匹配(靠TreeWalker跳过mark元素即可)
样式异常?重点检查 mark 的盒模型和暗色模式适配
高亮后文字错位、背景断开、颜色看不见,90% 是 CSS 没兜住。浏览器默认的 mark 样式极简,且不同 UA 差异大。
必须显式定义:
-
mark { background-color: #ffeb3b; color: #212121; padding: 0 2px; }—— 不依赖 UA 默认值 -
mark { display: inline; vertical-align: baseline; }—— 防止 flex 或 pre-line 下换行异常 -
@media (prefers-color-scheme: dark) { mark { background-color: #ffc107; color: #121212; } }—— 暗色模式下黄色背景必须加深,否则不可读 - 若父容器有
line-height: 1.2或font-size: 0.875rem,mark内部需继承,否则背景高度塌缩
多行高亮真正的难点不在“怎么标”,而在“标完还能否准确还原”——一旦用了字符串替换,range.getClientRects() 就会失效,后续拖选、复制、无障碍读屏都会出问题。坚持用 Range + mark + TreeWalker 这套组合,才能既稳定又可访问。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










