不使用document.body.innerHTML如何实现网页指定词汇高亮
无DOM破坏的页面词汇高亮实现方案
你当前使用的document.body.innerHTML全局替换方案会重建整个页面DOM树,原本绑定的事件监听、表单输入状态、运行时持有的DOM引用都会全部失效,确实不适合生产环境使用。
可行方案的核心逻辑是仅遍历操作文本节点,不改动原有元素节点的结构与属性,完全不会影响页面原有交互,具体实现如下:
实现思路
- 递归遍历DOM树,跳过
script/style/noscript/textarea/input这类不需要处理内容的标签 - 只对类型为文本(nodeType=3)的节点做关键词匹配
- 将匹配到关键词的文本节点拆分为「普通文本片段+高亮span节点」的组合,用文档碎片拼接后替换原文本节点
- 全程不修改其他已有元素的属性、结构,不会触发原有DOM的销毁重建
可直接使用的实现代码
function highlightKeyword(keyword, bgColor = 'YELLOW') { const skipTagSet = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT']); // 转义关键词里的正则特殊字符,避免匹配异常 const escapeReg = /[.*+?^${}()|[\]\\]/g; const matchReg = new RegExp(keyword.replace(escapeReg, '\\$&'), 'g'); function traverse(node) { // 处理元素节点:跳过黑名单标签,递归遍历子节点 if (node.nodeType === Node.ELEMENT_NODE) { if (skipTagSet.has(node.tagName)) return; // 倒序遍历子节点,避免节点替换导致的索引偏移问题 for (let i = node.childNodes.length - 1; i >= 0; i--) { traverse(node.childNodes[i]); } return; } // 处理文本节点:匹配关键词并拆分节点 if (node.nodeType === Node.TEXT_NODE) { const originText = node.textContent; matchReg.lastIndex = 0; if (!matchReg.test(originText)) return; matchReg.lastIndex = 0; const nodeFragment = document.createDocumentFragment(); let lastPos = 0; let matchRes; while ((matchRes = matchReg.exec(originText)) !== null) { // 插入匹配位置前的普通文本 if (matchRes.index > lastPos) { nodeFragment.appendChild( document.createTextNode(originText.slice(lastPos, matchRes.index)) ); } // 插入高亮节点 const highlightNode = document.createElement('span'); highlightNode.style.backgroundColor = bgColor; highlightNode.textContent = matchRes[0]; nodeFragment.appendChild(highlightNode); lastPos = matchReg.lastIndex; } // 插入末尾剩余的普通文本 if (lastPos < originText.length) { nodeFragment.appendChild( document.createTextNode(originText.slice(lastPos)) ); } // 替换原文本节点 node.parentNode.replaceChild(nodeFragment, node); } } traverse(document.body); } // 调用示例:高亮页面中所有的TEST关键词 highlightKeyword('TEST');
方案优势
- 不会破坏任何原有DOM的交互逻辑、事件绑定、表单状态
- 自动过滤不需要处理的特殊标签,不会误改脚本、样式、输入框内的内容
- 使用
DocumentFragment做节点批量更新,渲染性能更高
针对异步动态加载的内容,只需要在内容渲染完成后,对新增的DOM容器调用该方法即可完成增量高亮,不需要重复处理已经处理过的节点。
内容的提问来源于stack exchange,提问作者Vicky Lahkar
相关产品推荐
相关产品推荐

