如何从DOM中以相同的顺序创建标题标记的嵌套列表元素



所以我有一个HTML文档,其中包含任意数量的h1、h2、h3、h4等带有嵌套的标记。

示例。

<h2>About cats</h2>
<p>Some Info about cats</p>
<h2>About dogs</h2>
<p>Some Info about cats</p>
<h3>Breed 1</h3>
<p>About breed 1</p>
<h3>Breed 2</h3>
<p>About breed 2</p>
<h2>About birds</h2>
<p>Info about birds</p>

现在,我想要的是进行一些DOM遍历,获取所有的标题标签,通过使其内容蛇大小写为其添加id属性

<h2 id="about-dogs" >About Dogs</h2>

然后,创建一个包含以下内容的列表元素。

嵌套将根据标题标签的位置进行。意味着每个标题都将嵌套在第一个更高级别的标题中,依此类推

因此,如果只有一个h1,那么它将形成一个以h1为根、以最低级别的标题为叶的树。

<ul>
<li><a href="#about-cats" >About cats</a></li>
<li><a href="#about-dogs">About dogs</a></li>
<ul>
<li><a href='#breed-1' >Breed 1</a></li>
<li><a href='#breed-2' >Breed 1</a></li>
</ul>
<li><a href='#about-birds' >About birds</a></li>
</ul>

DEMO

function getHeaders() {
const hTags = ["h1", "h2", "h3", "h4", "h5", "h6"];
const elements = document.querySelectorAll(hTags.join());
const headers = [];
elements.forEach(el => {
const text = el.innerText;
const id = text
.toLowerCase()
.split(" ")
.join("-");
el.setAttribute("id", id);
headers.push({
id,
text,
level: hTags.indexOf(el.tagName.toLowerCase())
});
});
return headers;
}
function buildTree(headers) {
const list = [];
let nextLevelHeaders = [];
let lastLevel = -1;
if (headers.length === 0) {
return "";
}
const buildSubTree = () => {
if (nextLevelHeaders.length > 0) {
list[list.length - 1] += buildTree(nextLevelHeaders);
}
};
headers.forEach(h => {
if (lastLevel !== -1 && lastLevel < h.level) {
nextLevelHeaders.push(h);
return;
}
buildSubTree();
lastLevel = h.level;
list.push(`<a href="#${h.id}">${h.text}</a>`);
nextLevelHeaders = [];
});
buildSubTree();
const listHTML = list.map(i => `<li>${i}</li>`).join("");
return `<ul>${listHTML}</ul>`;
}
const headers = getHeaders();
document.querySelector("#root").innerHTML = buildTree(headers);
<div id="root"></div>
<h3>About horses (h3)</h3> <!-- corner case -->
<p>Some Info about horses</p>
<h2>About cats (h2)</h2>
<p>Some Info about cats</p>
<h2>About dogs (h2)</h2>
<p>Some Info about cats</p>
<h3>Breed 1 (h3)</h3>
<p>About breed 1</p>
<h3>Breed 2 (h3)</h3>
<p>About breed 2</p>
<h4>Breed 2.1 (h4)</h4>
<p>About breed 2.1</p>
<h4>Breed 2.2 (h4)</h4>
<p>About breed 2.2</p>
<h3>Breed 3 (h3)</h3>
<p>About breed 3</p>
<h3>Breed 4 (h3)</h3>
<p>About breed 4</p>
<h2>About birds (h2)</h2>
<p>Info about birds</p>
<h4>Bird <b>one</b> (h4)</h4><!-- corner case -->
<p>Info about birds</p>

好吧,我认为我们可以做得更好,但目前,这段代码可以满足您的期望,前提是您的html中有一个元素,其id为"strong";列表"将您的列表放入

在代码笔上看到它的作用

<nav id="list">
</nav>
let headers = document.querySelectorAll('h1, h2, h3')
let list = document.createElement('ul')
document.querySelector('#list')
.appendChild(list)
// list is now a ul in the nav section
let currentListLevel = 0
let currentListUl = list
let lastListItem = list
headers.forEach(h => {
let text = h.innerText
let level = h.tagName.slice(-1)
console.log(level + ':' + text)
let snakeCase = text.toLowerCase()
.trim()
.replace(' ', '-')

h.id = snakeCase // now title has id

let link = document.createElement('a')
// create the link
link.appendChild(document.createTextNode(text))
// give it the text of the header
link.href = '#' + snakeCase
// give it the reference to the header
let li = document.createElement('li')
li.appendChild(link)

if (level === currentListLevel) {
currentListUl.appendChild(li)
lastListItem = li
} else if (level > currentListLevel) {
currentListLevel = level
let ul = document.createElement('ul')
ul.level = level // store the level in a property
ul.appendChild(li)
lastListItem.appendChild(ul)
currentListUl = ul
lastListItem = li
} else if (level < currentListLevel) {
while (level < currentListLevel) {
currentListUl = currentListUl.parentNode
level = currentListUl.level
}
currentListUl.appendChild(li)
lastListItem = li
currentListLevel = level
}

})

最新更新