如何用原生JS实现类Markdown格式的Tooltip文本替换功能?
解决类Markdown Tooltip转换与DOM文本节点处理问题
我来帮你搞定这两个需求,先拆解下你原来代码的核心问题:直接操作innerHTML容易破坏DOM结构、正则只取第一个匹配项导致多短语失效、还额外依赖了*标记,不是你想要的纯[关键词]{提示内容}格式。下面给你一套完整的Vanilla JS实现方案:
一、核心思路
- 精准匹配目标格式:用正则
/\[([^\]]+)\]\{([^\}]+)\}/g全局匹配[关键词]{提示内容},捕获关键词和提示文本 - 操作文本节点而非innerHTML:遍历DOM中的纯文本节点,只处理包含目标格式的文本,避免破坏原有DOM结构(比如段落里的其他元素、绑定的事件)
- 批量替换并生成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




