You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用纯JavaScript在HTML字符串中搜索并高亮指定单词?

纯JavaScript实现HTML字符串中指定单词的高亮

直接对HTML字符串做全局替换很容易踩坑——比如不小心把标签属性里的"test"也给替换了(比如<img alt="test-image">里的test就会被误改)。正确的做法是借助DOM API来区分文本节点和标签内容,只高亮真正的文本内容,下面是完整的实现方案:

实现思路

  1. 将HTML字符串转换为可操作的DOM节点,避免直接字符串操作的风险
  2. 递归遍历所有节点,仅处理文本节点(跳过标签、注释等节点)
  3. 对每个文本节点,匹配目标关键词,将匹配部分用带高亮类的<span>包裹
  4. 最后将处理后的DOM节点转回HTML字符串

完整代码示例

function highlightTextInHtml(htmlStr, keyword) {
    // 创建临时DOM容器,用于解析HTML字符串
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = htmlStr;

    // 递归遍历节点的函数
    function traverseNodes(node) {
        // 如果是文本节点,处理高亮
        if (node.nodeType === Node.TEXT_NODE) {
            const text = node.textContent;
            // 构建正则:不区分大小写,匹配完整单词(避免部分匹配,比如"testing"里的test)
            // 如果不需要完整单词匹配,可以去掉\b
            const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
            
            // 如果文本中没有匹配,直接跳过
            if (!regex.test(text)) return;

            // 重置正则的lastIndex,避免多次匹配出问题
            regex.lastIndex = 0;

            // 拆分文本为匹配和非匹配的片段
            const fragments = text.split(regex);
            let match;
            const newNodes = [];

            // 遍历拆分后的片段,插入文本节点和高亮节点
            fragments.forEach((fragment, index) => {
                // 添加非匹配的文本片段
                if (fragment) {
                    newNodes.push(document.createTextNode(fragment));
                }
                // 获取下一个匹配项(因为split会把匹配项去掉,所以要补回来)
                match = regex.exec(text);
                if (match && index < fragments.length - 1) {
                    // 创建高亮span标签,添加自定义类
                    const highlightSpan = document.createElement('span');
                    highlightSpan.className = 'highlight';
                    highlightSpan.textContent = match[0];
                    newNodes.push(highlightSpan);
                }
            });

            // 替换原来的文本节点为新的节点集合
            node.replaceWith(...newNodes);
        } 
        // 如果是元素节点,递归遍历子节点
        else if (node.nodeType === Node.ELEMENT_NODE) {
            // 把子节点转成数组再遍历,避免遍历过程中节点变化导致的问题
            Array.from(node.childNodes).forEach(traverseNodes);
        }
    }

    // 开始遍历临时div的所有子节点
    Array.from(tempDiv.childNodes).forEach(traverseNodes);

    // 返回处理后的HTML字符串
    return tempDiv.innerHTML;
}

// 测试用例
const originalHtml = "&lt;html&gt; &lt;body&gt; &lt;p&gt;Test this test paragraph. Don't touch &lt;span class='test-class'&gt;this&lt;/span&gt;&lt;/p&gt; &lt;/body&gt; &lt;/html&gt;";
// 注意:先把HTML实体转义成真实的HTML标签,否则tempDiv解析的是实体字符串而不是标签
const decodedHtml = originalHtml.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
const highlightedHtml = highlightTextInHtml(decodedHtml, 'test');
console.log(highlightedHtml);

关键细节说明

  • 避免误改标签内容:通过DOM节点类型判断,只处理TEXT_NODE,完全不会碰标签的属性或结构
  • 正则匹配控制:使用\b确保匹配完整单词(如果需要匹配部分文本,比如"testing"里的"test",可以去掉\b),gi标志实现全局、不区分大小写匹配
  • 节点遍历安全:将子节点转为数组后再遍历,避免在修改节点时打乱遍历顺序
  • HTML实体处理:如果输入的是带有&lt;/&gt;的实体字符串,需要先转义为真实的HTML标签,否则DOM容器会把它们当成普通文本解析

高亮样式补充

最后别忘了在CSS里定义高亮类的样式,比如:

.highlight {
    background-color: yellow;
    font-weight: bold;
}

内容的提问来源于stack exchange,提问作者Milos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:47:21