如何在nwjs应用中通过文本选中实现不修改DOM的搜索高亮效果
不修改DOM实现关键词高亮(文本选中方案)
该方案完全不会修改原DOM结构,适配所有nwjs新版本的内核环境
实现核心思路
利用浏览器原生的Selection、Range以及CSS自定义高亮API实现关键词高亮,全程不会修改原有DOM节点,完全满足你的需求。
具体实现方案
基础版:单/多关键词统一高亮
原生Selection API支持通过JS批量添加选区,实现所有匹配关键词同时高亮的效果:
- 递归遍历页面所有可视文本节点,排除script、style等不可见节点的内容,避免匹配到无关文本
- 匹配到目标关键词的位置后,为每一处匹配结果创建独立的Range对象,绑定对应文本节点的起止偏移量
- 全局选区实例批量添加所有Range,即可实现全部匹配结果的高亮显示
代码示例
// 高亮指定关键词 function highlightKeywords(keyword) { const selection = window.getSelection() // 清空之前的高亮 selection.removeAllRanges() if (!keyword.trim()) return // 递归遍历文本节点匹配关键词 function traverseNodes(node) { const childNodes = node.childNodes for (let i = 0; i < childNodes.length; i++) { const current = childNodes[i] // 处理文本节点 if (current.nodeType === Node.TEXT_NODE && current.parentElement.offsetParent !== null) { const text = current.textContent let startPos = 0 // 匹配所有关键词位置 while (startPos < text.length) { const matchIndex = text.indexOf(keyword, startPos) if (matchIndex === -1) break // 创建选区范围 const range = document.createRange() range.setStart(current, matchIndex) range.setEnd(current, matchIndex + keyword.length) selection.addRange(range) startPos = matchIndex + keyword.length } } // 递归遍历普通元素节点 else if (current.nodeType === Node.ELEMENT_NODE && !['SCRIPT','STYLE','IFRAME'].includes(current.tagName)) { traverseNodes(current) } } } // 可替换为你需要搜索的指定容器,不需要遍历整个body提升性能 traverseNodes(document.body) } // 清空所有高亮 function clearHighlight() { window.getSelection().removeAllRanges() }
如果需要修改默认的选中高亮样式,在全局CSS中添加如下配置即可:
::selection { background-color: #ffeb3b; color: #000; }
进阶版:多关键词不同颜色高亮
如果需要同时匹配多个关键词,不同关键词显示不同高亮颜色,可以使用Chromium内核支持的CSS自定义高亮API,同样不会修改DOM:
代码示例
- 先添加全局CSS配置不同关键词的高亮样式:
::highlight(keyword-type-1) { background-color: #ffeb3b; color: #000; } ::highlight(keyword-type-2) { background-color: #4caf50; color: #fff; } ::highlight(keyword-type-3) { background-color: #f44336; color: #fff; }
- JS逻辑实现:
function highlightMultiKeywords(keywordList) { // 清空之前的自定义高亮 CSS.highlights.clear() if (!Array.isArray(keywordList) || keywordList.length === 0) return keywordList.forEach((keyword, index) => { const matchRanges = [] // 遍历文本节点匹配关键词 function traverseNodes(node) { const childNodes = node.childNodes for (let i = 0; i < childNodes.length; i++) { const current = childNodes[i] if (current.nodeType === Node.TEXT_NODE && current.parentElement.offsetParent) { const text = current.textContent let startPos = 0 while (startPos < text.length) { const matchIndex = text.indexOf(keyword, startPos) if (matchIndex === -1) break const range = document.createRange() range.setStart(current, matchIndex) range.setEnd(current, matchIndex + keyword.length) matchRanges.push(range) startPos = matchIndex + keyword.length } } else if (current.nodeType === Node.ELEMENT_NODE && !['SCRIPT','STYLE','IFRAME'].includes(current.tagName)) { traverseNodes(current) } } } traverseNodes(document.body) // 注册对应关键词的高亮样式 CSS.highlights.set(`keyword-type-${index+1}`, new Highlight(...matchRanges)) }) }
注意事项
- 可以限定遍历的DOM容器范围,不需要遍历整个body,大幅提升大页面的搜索性能
- 文本匹配逻辑可以根据需求调整为大小写不敏感、正则模糊匹配等,只需要修改对应匹配部分的代码即可
- 如果页面存在可编辑区域,可以提前过滤可编辑节点,避免和用户手动选区产生冲突
内容的提问来源于stack exchange,提问作者Gustavo Novais
相关产品推荐
相关产品推荐

