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

求助:优化基于用户搜索的JavaScript<mark>标签高亮功能

问题与解决方案:基于用户输入的列表文本高亮功能优化

问题描述

我正在开发一个功能:根据用户输入,用<mark>标签高亮列表项中的文本,且无法简化<li>标签的HTML结构。目前仅在简单场景下可用,例如搜索“Ba”时能正确生成如下结果:

<li>
  <input type="checkbox" name="hide-balise" id="hide-balise">
  <label for="hide-balise"><mark>Ba</mark>lise</label>
</li>

但遇到两个核心问题:

  1. 大小写不敏感匹配失效:输入小写“css”时,希望保留原HTML中的大写字母,生成<mark>CSS</mark>的高亮效果,但当前实现会强制替换为小写。
  2. 搜索词位置限制:当搜索内容位于文本中间或末尾时无法匹配,例如搜索“Sheet”无法添加<mark>标签。

这些问题与正则表达式的写法直接相关,需要优化正则及整体功能。

原代码

HTML

<input type="text" name="search">
<ul>
  <li>
    <input type="checkbox" name="hide-balise" id="hide-balise">
    <label for="hide-balise">Balise</label>
  </li>
  <li>
    <input type="checkbox" name="hide-div" id="hide-div">
    <label for="hide-div">
      <abbr lang="en" title="division">div</abbr>
    </label>
  </li>
  <li>
    <input type="checkbox" name="hide-div" id="hide-div-with-entities">
    <label for="hide-div-with-entities">
      <abbr lang="en" title="division">&lt;div&gt;</abbr>
    </label>
  </li>
  <li>
    <input type="checkbox" name="hide-attribut" id="hide-attribut">
    <label for="hide-attribut">Attribut</label></li>
  <li>
    <input type="checkbox" name="hide-css" id="hide-css">
    <label for="hide-css">
      <abbr lang="en" title="Cascading Style Sheets">mark</abbr>
      (<i lang="en">Cascading Style Sheets</i>)
    </label>
  </li>
  <li>
    <input type="checkbox" name="hide-attribut" id="hide-ancre">
    <label for="hide-ancre">Ancre (<i lang="en">Anchor</i>)</label>
  </li>
</ul>

JavaScript

const items = document.querySelectorAll('li');

function addMarks(event) {
  const regex = new RegExp(`&gt;[a-zA-Z]*${event.currentTarget.value}[a-zA-Z]*&lt;`, 'gi');
  for (const li of items) {
    li.innerHTML.match(regex)?.forEach((match) => {
      li.innerHTML = li.innerHTML.replace(
        match,
        match.replaceAll(event.currentTarget.value, `<mark>${event.currentTarget.value}</mark>`),
      );
    });
  }
}

function clearMarks() {
  items.forEach((li) => {
    li.innerHTML = li.innerHTML.replaceAll('<mark>', '');
    li.innerHTML = li.innerHTML.replaceAll('</mark>', '');
  });
}

document.querySelector('input[type=text]').addEventListener('input', (event) => {
  clearMarks();
  if (event.currentTarget.value.trim() !== '') {
    addMarks(event);
  }
});

问题分析

  1. 正则匹配范围狭窄:原正则&gt;[a-zA-Z]*${event.currentTarget.value}[a-zA-Z]*&lt;仅匹配标签首尾包裹的连续字母内容,无法处理文本中间或末尾的搜索词。
  2. 大小写丢失:替换时直接使用用户输入的文本覆盖原内容,导致原文本的大小写无法保留。
  3. innerHTML操作风险:直接修改innerHTML可能破坏原有的HTML标签结构(如<abbr>、<i>),且多次替换会导致性能问题。

优化后的代码

JavaScript

const items = document.querySelectorAll('li');

function addMarks(searchTerm) {
  // 转义正则特殊字符,避免输入特殊符号导致匹配异常
  const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  // 不区分大小写匹配搜索词,捕获原文本内容
  const regex = new RegExp(`(${escapedTerm})`, 'gi');
  
  items.forEach(li => {
    // 遍历所有文本节点,避免破坏原有HTML结构
    const treeWalker = document.createTreeWalker(li, NodeFilter.SHOW_TEXT, null, false);
    let currentNode;
    while (currentNode = treeWalker.nextNode()) {
      // 跳过已被mark包裹的文本,避免重复标记
      if (currentNode.parentElement.tagName !== 'MARK') {
        const originalText = currentNode.textContent;
        // 用捕获组保留原文本大小写,添加mark标签
        const highlightedText = originalText.replace(regex, '<mark>$1</mark>');
        if (highlightedText !== originalText) {
          const tempSpan = document.createElement('span');
          tempSpan.innerHTML = highlightedText;
          // 将原文本节点替换为高亮后的节点集合
          currentNode.replaceWith(...tempSpan.childNodes);
        }
      }
    }
  });
}

function clearMarks() {
  items.forEach(li => {
    const marks = li.querySelectorAll('mark');
    marks.forEach(mark => {
      // 移除mark标签,保留其内部内容
      mark.replaceWith(...mark.childNodes);
    });
  });
}

document.querySelector('input[type=text]').addEventListener('input', (event) => {
  const searchTerm = event.currentTarget.value.trim();
  clearMarks();
  if (searchTerm) {
    addMarks(searchTerm);
  }
});

关键改进点

  • 正则转义:对搜索词中的正则特殊字符(如.、*)进行转义,确保搜索词被精确匹配。
  • 文本节点遍历:使用TreeWalker遍历纯文本节点,直接操作文本内容,完全保留原HTML标签结构。
  • 大小写保留:通过正则捕获组$1直接使用匹配到的原文本内容包裹<mark>,完美保留原文本的大小写格式。
  • 任意位置匹配:正则(${escapedTerm})会匹配文本中任意位置的搜索词,不管是开头、中间还是末尾。
  • 避免重复标记:检查文本节点的父元素是否为<mark>,防止重复添加高亮标签。
  • 高效清除高亮:直接移除<mark>标签并保留其内容,比多次替换innerHTML更高效且安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:35:55