You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用原生JS实现类Markdown格式的Tooltip文本替换功能?

解决类Markdown Tooltip转换与DOM文本节点处理问题

我来帮你搞定这两个需求,先拆解下你原来代码的核心问题:直接操作innerHTML容易破坏DOM结构、正则只取第一个匹配项导致多短语失效、还额外依赖了*标记,不是你想要的纯[关键词]{提示内容}格式。下面给你一套完整的Vanilla JS实现方案:

一、核心思路

  1. 精准匹配目标格式:用正则/\[([^\]]+)\]\{([^\}]+)\}/g全局匹配[关键词]{提示内容},捕获关键词和提示文本
  2. 操作文本节点而非innerHTML:遍历DOM中的纯文本节点,只处理包含目标格式的文本,避免破坏原有DOM结构(比如段落里的其他元素、绑定的事件)
  3. 批量替换并生成DOM元素:把匹配到的短语转换成带Bootstrap Tooltip属性的<mark>标签,替换原文本节点

二、完整实现代码

// 遍历DOM树中的所有文本节点的工具函数
function traverseTextNodes(node, callback) {
  if (node.nodeType === Node.TEXT_NODE) {
    callback(node);
  } else {
    // 递归遍历子节点,跳过script/style等非文本容器
    if (node.tagName !== 'SCRIPT' && node.tagName !== 'STYLE') {
      Array.from(node.childNodes).forEach(child => traverseTextNodes(child, callback));
    }
  }
}

// 匹配[关键词]{提示内容}的正则(全局匹配)
const tooltipPattern = /\[([^\]]+)\]\{([^\}]+)\}/g;

// 处理所有段落中的tooltip格式文本
document.querySelectorAll('p').forEach(paragraph => {
  traverseTextNodes(paragraph, textNode => {
    const originalText = textNode.textContent;
    // 如果当前文本节点没有匹配项,直接跳过
    if (!tooltipPattern.test(originalText)) return;

    // 创建文档片段来存放拆分后的文本和元素(避免频繁DOM操作)
    const fragment = document.createDocumentFragment();
    let lastPosition = 0;

    // 重置正则的匹配指针,避免全局匹配时的残留状态
    tooltipPattern.lastIndex = 0;
    let matchResult;

    // 循环处理所有匹配项
    while ((matchResult = tooltipPattern.exec(originalText)) !== null) {
      const matchStart = matchResult.index;
      const matchLength = matchResult[0].length;
      const keyword = matchResult[1];
      const tooltipText = matchResult[2];

      // 添加匹配项之前的普通文本
      if (matchStart > lastPosition) {
        fragment.appendChild(document.createTextNode(originalText.slice(lastPosition, matchStart)));
      }

      // 创建带Bootstrap Tooltip属性的mark标签
      const tooltipElement = document.createElement('mark');
      tooltipElement.dataset.toggle = 'tooltip';
      tooltipElement.dataset.placement = 'top';
      tooltipElement.title = tooltipText;
      tooltipElement.textContent = keyword;

      fragment.appendChild(tooltipElement);
      lastPosition = matchStart + matchLength;
    }

    // 添加剩余的普通文本
    if (lastPosition < originalText.length) {
      fragment.appendChild(document.createTextNode(originalText.slice(lastPosition)));
    }

    // 用文档片段替换原来的文本节点
    textNode.parentNode.replaceChild(fragment, textNode);
  });
});

// 初始化Bootstrap Tooltip功能
document.querySelectorAll('[data-toggle="tooltip"]').forEach(element => {
  new bootstrap.Tooltip(element);
});

三、方案优势

  • 支持多短语/多段落:不管一个段落里有多少个[关键词]{提示},都能逐个处理
  • 不破坏原有DOM:只操作纯文本节点,段落里的其他元素(比如链接、加粗标签)不会被影响
  • 无需额外标记:直接匹配你想要的[关键词]{提示内容}格式,不用加多余的*
  • 性能友好:用文档片段批量操作DOM,减少重绘重排

四、扩展优化建议

  • 如果页面有动态加载的内容,可以把处理逻辑封装成函数,动态内容加载完成后调用一次
  • 可以把tooltip的placement(top/right/bottom/left)做成可配置参数,满足不同布局需求
  • 要是需要支持转义字符(比如关键词里的[]),可以调整正则为/\[([^\]\\]|\\.)+\]\{([^\}\\]|\\.)+\}/g,处理转义场景

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

火山引擎 最新活动