在TypeScript遍历DOM时如何追踪最近遇到的HTML标题ID
问题:如何跟踪最近遇到的标题ID并关联到所有文本节点?
我有一个TypeScript/React应用,使用marked.js将Markdown文件转换为HTML并展示。当前代码遍历文本节点,提取所有显示的原始文本值存入searchIndexReference,每个文本对应一个数字ID,但无法正确跟踪最近遇到的标题ID并关联到后续文本节点——当标题包含<a>、<em>这类嵌套元素时,文本节点的直接父元素不是标题,导致标题ID无法被获取,最终headerReference中的数据不符合预期。
当前使用的代码片段:
let id: number = 1; let headerIdFromHTML = ''; const headerTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']; files.forEach(file => { // convert markdown content to html using marked.js const htmlString = marked(file.markDown); const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); const walker = document.createTreeWalker(doc, NodeFilter.SHOW_TEXT); let currentNode = walker.currentNode; // gather raw text from every HTML element while (currentNode != null) { if (currentNode.textContent != null) { if (currentNode.parentElement) { // only care about h1-h6 if (headerTags.includes(currentNode.parentElement.tagName)) { // store most recently seen header id, update when new header encountered headerIdFromHTML = currentNode.parentElement.id; } } searchIndexReference.push({ id, text: currentNode.textContent }); headerReference.push({ id, source: file.source, headerId: headerIdFromHTML }); id++; } const nextNode = walker.nextNode(); if (nextNode != null) { currentNode = nextNode; } else { break; } } });
问题复现
以下HTML示例中,最后两个元素的ID无法被正确获取:
<h3 id="banner">BANNER</h3> <h1 id="project-name">Project Name</h1> <h2 id="quick-links-a-nameproject_linksa">Quick Links <a name="project_links"></a></h2> <li><h5 id="basic-components"><a href="#project_components">Basic Components</a></h5></li> <h4 id="general"><em>General</em></h4>
期望结果
headerReference应生成如下数据:
{id: 1, headerId: 'banner', text: 'BANNER'} {id: 2, headerId: 'project-name', text: 'Project Name'} {id: 3, headerId: 'quick-links-a-nameproject_linksa', text: 'Quick Links'} {id: 4, headerId: 'basic-components', text: 'Basic Components'} {id: 5, headerId: 'general', text: 'General'}
解决方案
问题根源是当前代码只检查文本节点的直接父元素是否为标题,无法处理嵌套元素的情况。解决思路是:对每个文本节点,向上遍历DOM树,找到最近的标题元素(h1-h6),用它的ID更新当前跟踪的标题ID;如果找不到新标题,则沿用之前的ID。
修改后的完整代码:
let id: number = 1; let headerIdFromHTML = ''; // 工具函数:向上查找最近的标题元素,返回其ID const findClosestHeader = (node: Node | null): string => { if (!node) return ''; const element = node instanceof Element ? node : node.parentElement; if (!element) return ''; const tagName = element.tagName.toLowerCase(); if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) { return element.id || ''; } return findClosestHeader(element.parentElement); }; files.forEach(file => { const htmlString = marked(file.markDown); const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); const walker = document.createTreeWalker(doc, NodeFilter.SHOW_TEXT); let currentNode = walker.currentNode; while (currentNode != null) { if (currentNode.textContent != null) { const closestHeaderId = findClosestHeader(currentNode); // 找到新标题ID则更新跟踪值 if (closestHeaderId) { headerIdFromHTML = closestHeaderId; } // 可选:trim()去除文本前后空格,避免无效空文本存入 const cleanText = currentNode.textContent.trim(); if (cleanText) { searchIndexReference.push({ id, text: cleanText }); headerReference.push({ id, source: file.source, headerId: headerIdFromHTML }); id++; } } currentNode = walker.nextNode(); } });
关键修改说明
- 新增
findClosestHeader工具函数:递归向上遍历DOM树,直到找到最近的h1-h6元素,返回其ID;如果遍历到根节点仍未找到,返回空字符串。 - 替换标题ID检测逻辑:不再只检查直接父元素,而是调用工具函数查找最近标题,确保嵌套元素内的文本节点也能关联到正确的标题ID。
- 优化标签判断:统一将标签名转小写,无需维护大小写重复的标签数组。
- 可选文本清理:增加
trim()和空文本判断,避免多余空格或空文本被存入索引。
这样修改后,无论标题内嵌套多少层元素,都能正确跟踪最近的标题ID,生成符合预期的headerReference数据。
内容的提问来源于stack exchange,提问作者xXx_emo_girl_xXx
相关产品推荐
相关产品推荐

