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

如何用纯JavaScript高亮元素内匹配搜索词的文本且不影响元素属性

实现思路

要实现不修改元素属性的搜索高亮,核心原则是仅操作DOM树中的纯文本节点,完全避开标签、属性的内容处理,步骤如下:

  • 每次触发搜索前先清理之前添加的<mark>高亮标签,避免多次搜索出现标签嵌套
  • 递归遍历目标元素的所有子节点:遇到元素节点就继续遍历其子节点,遇到纯文本节点再做内容匹配
  • 对匹配到的搜索词,通过DOM节点拆分的方式插入<mark>标签,而非直接修改innerHTML
完整可运行代码
<input type='text' id='search' onkeyup="highlight(this.value)">
<p id='WE1'>
  <b>WE</b>wE & they
  <a href="url_With_WE_we_wE_WeOnly">them and We</a>
  <span id="we2">we only.</span>
</p>

<script>
function highlight(searchedWords) {
  const targetEl = document.getElementById("WE1");
  // 先清空之前的高亮:释放mark标签内的内容,删除mark标签
  clearHighlight(targetEl);

  const words = searchedWords.trim().split(/\s+/).filter(w => w.length > 0);
  if (words.length === 0) return;

  // 生成全局匹配正则,大小写不敏感,多个搜索词用|分隔
  const regex = new RegExp(words.map(w => escapeRegExp(w)).join('|'), 'gi');

  // 递归遍历所有文本节点做替换
  traverseTextNodes(targetEl, (textNode) => {
    const text = textNode.nodeValue;
    const matches = text.match(regex);
    if (!matches) return;

    // 拆分文本节点,插入mark标签
    let lastIndex = 0;
    const fragment = document.createDocumentFragment();
    text.replace(regex, (match, index) => {
      // 插入匹配位置之前的文本
      if (index > lastIndex) {
        fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)));
      }
      // 插入带高亮的匹配内容
      const mark = document.createElement('mark');
      mark.textContent = match;
      fragment.appendChild(mark);
      lastIndex = index + match.length;
    });
    // 插入最后剩余的文本
    if (lastIndex < text.length) {
      fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
    }
    // 替换原来的文本节点
    textNode.parentNode.replaceChild(fragment, textNode);
  });
}

// 工具函数:转义正则特殊字符,避免搜索词包含./*等字符时报错
function escapeRegExp(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// 工具函数:清理之前的高亮
function clearHighlight(el) {
  const marks = el.querySelectorAll('mark');
  marks.forEach(mark => {
    const parent = mark.parentNode;
    parent.replaceChild(document.createTextNode(mark.textContent), mark);
    // 合并相邻文本节点,避免节点碎片化
    parent.normalize();
  });
}

// 工具函数:遍历所有纯文本节点,跳过不需要处理的元素
function traverseTextNodes(el, callback) {
  const childNodes = el.childNodes;
  for (let i = 0; i < childNodes.length; i++) {
    const node = childNodes[i];
    // 跳过mark、script、style等无需处理的元素
    if (node.nodeType === Node.ELEMENT_NODE) {
      if (['MARK', 'SCRIPT', 'STYLE', 'IFRAME'].includes(node.tagName)) continue;
      traverseTextNodes(node, callback);
    } else if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
      callback(node);
    }
  }
}
</script>
方案优势
  • 完全不会修改元素属性:全程仅操作纯文本节点,不会触及标签、href、id等任何属性内容,a标签等元素完全不受影响
  • 大小写不敏感:正则使用gi标识,匹配时忽略大小写,同时保留原始文本的大小写展示
  • 兼容性好:所有API都是DOM基础API,兼容所有主流浏览器,不存在replaceAll的兼容性问题
  • 支持特殊字符搜索:增加了正则转义逻辑,搜索词包含.、*、+等正则特殊字符也不会报错
  • 多次搜索不会错乱:每次搜索前先清空之前的高亮,不会出现标签嵌套问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 19:24:03