一.本人用的markdown插件是cherry-markdown,个人觉得比较好用,画图和数学公式都整合的很好
https://github.com/Tencent/cherry-markdown
二.背景
经过markdown解析的html,要取里面的h标签转换成目录树,发现这里面都要人工计算,没有发现有插件可以解析
三.效果图
四.js原生代码方法
1.方法一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.a-menu {
height: calc(100vh - 334px);
overflow: hidden;
padding-right: 8px;
padding-top: 8px;
padding-bottom: 8px;
background: rgba(251, 251, 251, 0);
transition: ease-in-out 0.16s;
}
@media screen and (max-width: 640px) {
.a-menu {
height: calc(4vh);
}
}
.a-menu::-webkit-scrollbar {
width: 7px;
}
.a-menu::-webkit-scrollbar-thumb {
background: #d9dde6;
transition: ease-in-out 0.32s;
}
.a-menu::-webkit-scrollbar-thumb:hover {
background: #cbd0db;
transition: ease-in-out 0.32s;
}
.a-menu:hover {
overflow-y: auto;
transition: ease-in-out 0.16s;
}
.a-menu .a-list {
display: flex;
flex-direction: column;
padding-top: 4px;
box-sizing: border-box;
}
.a-menu .a-list.is-child .a-list-content a {
padding: 2px 0 2px 16px;
font-size: 12px;
}
.a-menu .a-list .a-list-content {
display: inline-flex;
flex-direction: column;
margin-left: -2px;
font-size: 14px;
color: #666666;
}
.a-menu .a-list .a-list-content .a-list .a-list-content {
display: block;
position: relative;
}
.a-menu .a-list .a-list-content .a-list .a-list-content a {
padding-right: 0;
}
.a-menu .a-list .a-list-content .a-list .a-list-content.colorActive::after {
background-color: #248bff;
}
.a-menu .a-list .a-list-content .a-list .a-list-content::after {
content: '';
display: inline-block;
position: absolute;
left: 4px;
top: 0;
bottom: 0;
width: 1px;
background-color: #dfdfdf;
}
.a-menu .a-list .a-list-content a {
display: inline-flex;
align-items: center;
margin: 4px 0;
padding-right: 20px;
position: relative;
color: #999;
overflow: hidden;
}
.a-menu .a-list .a-list-content a .space-inner {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.a-menu .a-list .a-list-content a:hover {
color: #165dff;
}
.a-menu .a-list .a-list-content a:hover.has-children::before {
border-bottom-color: #248bff;
border-right-color: #248bff;
}
.a-menu .a-list .a-list-content a.has-children {
display: flex;
color: #000;
}
.a-menu .a-list .a-list-content a.has-children::before {
content: '';
display: flex;
width: 6px;
height: 6px;
position: absolute;
right: 0px;
top: 2px;
transform: rotate(45deg);
border-bottom: 1px solid #C9CDD4;
border-right: 1px solid #C9CDD4;
}
.a-menu .a-list .a-list-content a.is-open::before {
top: 6px;
transform: rotate(-135deg);
}
.a-menu .a-list .a-list-content a:not(.is-open) + .a-list {
display: none;
}
.a-menu .a-list .a-list-content a.activeLink {
color: #248bff;
}
.a-menu > .a-list > .a-list-content > a {
font-size: 14px;
color: #000;
}
</style>
<body>
<div class="a-menu" id="menuBox">
</div>
<script>
let contentHtmlStr = `<div data-inline-code-theme="red" data-code-block-theme="tomorrow night"><h1 data-lines="1" data-sign="70af8ccd3da1fa06b19ac1bbab643567" id="一级目录"><a href="#一级目录" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="b961c3193476d88b1d341e95b5d80327" id="二级目录"><a href="#二级目录" class="anchor"></a><strong>二级目录</strong></h2><h1 data-lines="1" data-sign="25aae3b2a2128daa60fd958f2bcd3d2d" id="一级目录-3"><a href="#一级目录-3" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="08fddbf5db1e7127bc127ee6d4727500" id="三级目录"><a href="#三级目录" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="e7b867ff368ac6b72c919ed3512bad68" id="一级目录-4"><a href="#一级目录-4" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="5e46a853fffe62d8533d1ec70c30d75e" id="二级目录-3"><a href="#二级目录-3" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="35c88e5d8a4fe471473ff3399fb80d67" id="三级目录-3"><a href="#三级目录-3" class="anchor"></a><strong>三级目录</strong></h3><h2 data-lines="1" data-sign="e84f6131c5fa7a71ccce5ede7f6d4f44" id="二级目录-4"><a href="#二级目录-4" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="42140303320892247936dbddd25f6437" id="三级目录-4"><a href="#三级目录-4" class="anchor"></a><strong>三级目录</strong></h3><h2 data-lines="1" data-sign="6841f56875dca1f8202c2d107728d51e" id="二级目录-5"><a href="#二级目录-5" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="e2e87e66fe9a4d690d5b39295f68db9b" id="三级目录-5"><a href="#三级目录-5" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="60fc5695a612f0345a887c03e2270fc2" id="四级目录"><a href="#四级目录" class="anchor"></a><strong>四级目录</strong></h4><h3 data-lines="1" data-sign="44539014907c9a6576c85d3f680f7451" id="三级目录-6"><a href="#三级目录-6" class="anchor"></a><strong>三级目录</strong></h3><h3 data-lines="1" data-sign="b46bc9232cf0159857bf60b3aa2c5195" id="三级目录-7"><a href="#三级目录-7" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="d71970fb2368a14fc5aa82e29129173c" id="一级目录-5"><a href="#一级目录-5" class="anchor"></a><strong>一级目录</strong></h1><h4 data-lines="1" data-sign="f9812f5521a2221003e49755afcc29c6" id="四级目录-3"><a href="#四级目录-3" class="anchor"></a><strong>四级目录</strong></h4><h1 data-lines="1" data-sign="e46816d23a866b8523bf08e1d391ab7d" id="一级目录-6"><a href="#一级目录-6" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="67fbe72a8d079f663ccbda69ee668da0" id="三级目录-8"><a href="#三级目录-8" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="3c5a1ea69b73db6836bfbf3bb2224c26" id="一级目录-7"><a href="#一级目录-7" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="3fc89b827eb2b5a4d54859e843cff7d5" id="二级目录-6"><a href="#二级目录-6" class="anchor"></a><strong>二级目录</strong></h2><h1 data-lines="1" data-sign="5dee0f6d3936fc8911a76a904223b3d9" id="一级目录-8"><a href="#一级目录-8" class="anchor"></a><strong>一级目录</strong></h1><h4 data-lines="1" data-sign="13b7a1aecb892fe01daf9d307832d5c0" id="四级目录-4"><a href="#四级目录-4" class="anchor"></a><strong>四级目录</strong></h4><h1 data-lines="1" data-sign="d1dbf5c2c258c99a96adc878965ef1f0" id="一级目录-9"><a href="#一级目录-9" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="a67216f009b8c8d794a9b7fbfab117ce" id="三级目录-9"><a href="#三级目录-9" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="0eaafae5a4e4f62a672c9829163a936b" id="一级目录-10"><a href="#一级目录-10" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="56a41f5ab1aa716f10386e3198e1894d" id="二级目录-7"><a href="#二级目录-7" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="15462102af967ea2a4d18f7cdab278e0" id="三级目录-10"><a href="#三级目录-10" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="d20063c4955759c16f40a54a6fe0e223" id="四级目录-5"><a href="#四级目录-5" class="anchor"></a><strong>四级目录</strong></h4><h5 data-lines="1" data-sign="4f474aa0317f5fab32746ed19e8b0a08" id="五级目录"><a href="#五级目录" class="anchor"></a><strong>五级目录</strong></h5><h6 data-lines="1" data-sign="e54b3244c958b60083fd0fd1f0a79448" id="六级目录"><a href="#六级目录" class="anchor"></a><strong>六级目录</strong></h6><h1 data-lines="1" data-sign="b0d40797eed445eb5e29a011e7fe1e1f" id="一级目录-11"><a href="#一级目录-11" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="f055a5ed87cfe7934262638b5ea1e076" id="二级目录-8"><a href="#二级目录-8" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="5ed4b1b9e11053095450d803a66e8595" id="三级目录-11"><a href="#三级目录-11" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="6ac151d6f6d0a08da7eda2ff1b644280" id="四级目录-6"><a href="#四级目录-6" class="anchor"></a><strong>四级目录</strong></h4><h5 data-lines="1" data-sign="de2883023dc43dda752c05db796f8739" id="五级目录-3"><a href="#五级目录-3" class="anchor"></a><strong>五级目录</strong></h5><h6 data-lines="1" data-sign="71084d01bb4fa484cab4b75b7d531bd2" id="六级目录-3"><a href="#六级目录-3" class="anchor"></a><strong>六级目录</strong></h6></div>`
//获取当前dom应该向左边移动的位置,因为是目录树,移动有继承关系,这边需要计算
function getItemPaddingLeft(item){
let defaultVal = 16;
let maxTreeLevel =1;
//目录层级等于1是最大层级,上面没有父类
if(item.treeLevel == 1){
return defaultVal+'px';
}
//当前不是目录树最大层级,上面没有父类
if(item.pid == 0){
return (defaultVal+16*(item.treeLevel-maxTreeLevel))+'px';
}
//剩下的就是有父类,不是最大层级(这一类不用加默认距离,因为父类已经加,会继承)
return (16*(item.treeLevel-item.parentTreeLevel))+'px';
}
function getContentTree(contentHtmlStr) {
if(!contentHtmlStr) {
return [];
}
let hDomList = contentHtmlStr.match(
/<h[1-6]{1}[^>]*>([\s\S]*?)<\/h[1-6]{1}>/g
);
if(!Array.isArray(hDomList)){
return [];
}
const dirTree = [];
//最大的h标签值
let maxhLevel;
hDomList.forEach((hDom,index)=>{
const startIndex = hDom.indexOf("id=\"");
const endIndex = hDom.indexOf("\">");
if(startIndex!=-1 && endIndex!=-1){
const re = /<h([1-6]{1})[^>]*>([\s\S]*?)<\/h[1-6]{1}>/
let hLevel = Number(hDom.replace(re, "$1"));
const domId = hDom.slice(startIndex+4,endIndex);
let titleContent = hDom.replace(re, "$2");
// 有可能匹配到的是这样'我<strong>是</strong>h1<em>斜体</em>介绍' 里面还有标签(直接去掉标签)
let title = titleContent.replaceAll(/<([\s\S]*?)>|<\/([\s\S]*?)>/g, "");
const currentDir = {
id:(index+1),//用于点击的时候选中态
title,
domId,
hLevel,//h标签值(1,2,3,4,5,6)
isOpen:true
}
if(dirTree.length>0){
//查找最近的父类(将数组反转才能找到最近的)
//reverse会改变原数组,JSON.parse(JSON.stringify())方法去一下生成新数组
let reverseDirTree = JSON.parse(JSON.stringify(dirTree)).reverse();
let findParent = reverseDirTree.find(i=>currentDir.hLevel > i.hLevel);
if(findParent){
currentDir.pid = findParent.id;
}else{
currentDir.pid = 0;
}
}else{
currentDir.pid = 0;
}
if(maxhLevel){
if(currentDir.hLevel < maxhLevel){
maxhLevel = currentDir.hLevel;
}
}else{
maxhLevel = currentDir.hLevel;
}
dirTree.push(currentDir)
}
})
dirTree.forEach((item)=>{
item.parentTreeLevel = 0;//父类默认是0
item.treeLevel = (item.hLevel - maxhLevel) +1;//目录树层级,也代表当前h标签解析的内容在父类属于第几层级,1是最大层级
if(item.pid!==0){
const findParent = dirTree.find(i=>i.id === item.pid);
if(findParent){
if(!Array.isArray(findParent.children)){
findParent.children = []
}
item.parentTreeLevel = (findParent.hLevel - maxhLevel) +1;//父目录树层级,也代表当前h标签解析的内容在父类属于第几层级,1是最大层级
findParent.children.push(item)
}
}
})
return dirTree.filter((i)=>i.pid === 0);
}
let contentTree = getContentTree(contentHtmlStr);
let contentTreeHtml = '';
function calcContentTreeHtml(contentTree) {
if (contentTree.length > 0) {
contentTreeHtml +=`<div class="a-list">`
for (let i in contentTree) {
contentTreeHtml +=`
<div
style="padding-left:${getItemPaddingLeft(contentTree[i])}"
class="a-list-content"
>
<a class="${(Array.isArray(contentTree[i].children) && contentTree[i].children.length>0 && contentTree[i].isOpen)?'is-open':''} ${(Array.isArray(contentTree[i].children) && contentTree[i].children.length>0)?'has-children':''}">
<span class="space-inner" title="${contentTree[i].title}">${contentTree[i].title}</span>
</a>
`
if (Array.isArray(contentTree[i].children) && contentTree[i].children.length>0) {
calcContentTreeHtml(contentTree[i].children);
}
contentTreeHtml += `</div>`;
}
contentTreeHtml += `</div>`;
}
}
calcContentTreeHtml(contentTree);
document.getElementById('menuBox').innerHTML = contentTreeHtml;
</script>
</body>
</html>
2.方法二:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
.a-menu {
height: calc(100vh - 334px);
overflow: hidden;
padding-right: 8px;
padding-top: 8px;
padding-bottom: 8px;
background: rgba(251, 251, 251, 0);
transition: ease-in-out 0.16s;
}
@media screen and (max-width: 640px) {
.a-menu {
height: calc(4vh);
}
}
.a-menu::-webkit-scrollbar {
width: 7px;
}
.a-menu::-webkit-scrollbar-thumb {
background: #d9dde6;
transition: ease-in-out 0.32s;
}
.a-menu::-webkit-scrollbar-thumb:hover {
background: #cbd0db;
transition: ease-in-out 0.32s;
}
.a-menu:hover {
overflow-y: auto;
transition: ease-in-out 0.16s;
}
.a-menu .a-list {
display: flex;
flex-direction: column;
padding-top: 4px;
box-sizing: border-box;
}
.a-menu .a-list.is-child .a-list-content a {
padding: 2px 0 2px 16px;
font-size: 12px;
}
.a-menu .a-list .a-list-content {
display: inline-flex;
flex-direction: column;
margin-left: -2px;
font-size: 14px;
color: #666666;
}
.a-menu .a-list .a-list-content .a-list .a-list-content {
display: block;
position: relative;
}
.a-menu .a-list .a-list-content .a-list .a-list-content a {
padding-right: 0;
}
.a-menu .a-list .a-list-content .a-list .a-list-content.colorActive::after {
background-color: #248bff;
}
.a-menu .a-list .a-list-content .a-list .a-list-content::after {
content: '';
display: inline-block;
position: absolute;
left: 4px;
top: 0;
bottom: 0;
width: 1px;
background-color: #dfdfdf;
}
.a-menu .a-list .a-list-content a {
display: inline-flex;
align-items: center;
margin: 4px 0;
padding-right: 20px;
position: relative;
color: #999;
overflow: hidden;
}
.a-menu .a-list .a-list-content a .space-inner {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.a-menu .a-list .a-list-content a:hover {
color: #165dff;
}
.a-menu .a-list .a-list-content a:hover.has-children::before {
border-bottom-color: #248bff;
border-right-color: #248bff;
}
.a-menu .a-list .a-list-content a.has-children {
display: flex;
color: #000;
}
.a-menu .a-list .a-list-content a.has-children::before {
content: '';
display: flex;
width: 6px;
height: 6px;
position: absolute;
right: 0px;
top: 2px;
transform: rotate(45deg);
border-bottom: 1px solid #C9CDD4;
border-right: 1px solid #C9CDD4;
}
.a-menu .a-list .a-list-content a.is-open::before {
top: 6px;
transform: rotate(-135deg);
}
.a-menu .a-list .a-list-content a:not(.is-open) + .a-list {
display: none;
}
.a-menu .a-list .a-list-content a.activeLink {
color: #248bff;
}
.a-menu > .a-list > .a-list-content > a {
font-size: 14px;
color: #000;
}
</style>
<body>
<div class="a-menu" id="menuBox">
</div>
<script>
let contentHtmlStr = `<div data-inline-code-theme="red" data-code-block-theme="tomorrow night"><h1 data-lines="1" data-sign="70af8ccd3da1fa06b19ac1bbab643567" id="一级目录"><a href="#一级目录" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="b961c3193476d88b1d341e95b5d80327" id="二级目录"><a href="#二级目录" class="anchor"></a><strong>二级目录</strong></h2><h1 data-lines="1" data-sign="25aae3b2a2128daa60fd958f2bcd3d2d" id="一级目录-3"><a href="#一级目录-3" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="08fddbf5db1e7127bc127ee6d4727500" id="三级目录"><a href="#三级目录" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="e7b867ff368ac6b72c919ed3512bad68" id="一级目录-4"><a href="#一级目录-4" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="5e46a853fffe62d8533d1ec70c30d75e" id="二级目录-3"><a href="#二级目录-3" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="35c88e5d8a4fe471473ff3399fb80d67" id="三级目录-3"><a href="#三级目录-3" class="anchor"></a><strong>三级目录</strong></h3><h2 data-lines="1" data-sign="e84f6131c5fa7a71ccce5ede7f6d4f44" id="二级目录-4"><a href="#二级目录-4" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="42140303320892247936dbddd25f6437" id="三级目录-4"><a href="#三级目录-4" class="anchor"></a><strong>三级目录</strong></h3><h2 data-lines="1" data-sign="6841f56875dca1f8202c2d107728d51e" id="二级目录-5"><a href="#二级目录-5" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="e2e87e66fe9a4d690d5b39295f68db9b" id="三级目录-5"><a href="#三级目录-5" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="60fc5695a612f0345a887c03e2270fc2" id="四级目录"><a href="#四级目录" class="anchor"></a><strong>四级目录</strong></h4><h3 data-lines="1" data-sign="44539014907c9a6576c85d3f680f7451" id="三级目录-6"><a href="#三级目录-6" class="anchor"></a><strong>三级目录</strong></h3><h3 data-lines="1" data-sign="b46bc9232cf0159857bf60b3aa2c5195" id="三级目录-7"><a href="#三级目录-7" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="d71970fb2368a14fc5aa82e29129173c" id="一级目录-5"><a href="#一级目录-5" class="anchor"></a><strong>一级目录</strong></h1><h4 data-lines="1" data-sign="f9812f5521a2221003e49755afcc29c6" id="四级目录-3"><a href="#四级目录-3" class="anchor"></a><strong>四级目录</strong></h4><h1 data-lines="1" data-sign="e46816d23a866b8523bf08e1d391ab7d" id="一级目录-6"><a href="#一级目录-6" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="67fbe72a8d079f663ccbda69ee668da0" id="三级目录-8"><a href="#三级目录-8" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="3c5a1ea69b73db6836bfbf3bb2224c26" id="一级目录-7"><a href="#一级目录-7" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="3fc89b827eb2b5a4d54859e843cff7d5" id="二级目录-6"><a href="#二级目录-6" class="anchor"></a><strong>二级目录</strong></h2><h1 data-lines="1" data-sign="5dee0f6d3936fc8911a76a904223b3d9" id="一级目录-8"><a href="#一级目录-8" class="anchor"></a><strong>一级目录</strong></h1><h4 data-lines="1" data-sign="13b7a1aecb892fe01daf9d307832d5c0" id="四级目录-4"><a href="#四级目录-4" class="anchor"></a><strong>四级目录</strong></h4><h1 data-lines="1" data-sign="d1dbf5c2c258c99a96adc878965ef1f0" id="一级目录-9"><a href="#一级目录-9" class="anchor"></a><strong>一级目录</strong></h1><h3 data-lines="1" data-sign="a67216f009b8c8d794a9b7fbfab117ce" id="三级目录-9"><a href="#三级目录-9" class="anchor"></a><strong>三级目录</strong></h3><h1 data-lines="1" data-sign="0eaafae5a4e4f62a672c9829163a936b" id="一级目录-10"><a href="#一级目录-10" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="56a41f5ab1aa716f10386e3198e1894d" id="二级目录-7"><a href="#二级目录-7" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="15462102af967ea2a4d18f7cdab278e0" id="三级目录-10"><a href="#三级目录-10" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="d20063c4955759c16f40a54a6fe0e223" id="四级目录-5"><a href="#四级目录-5" class="anchor"></a><strong>四级目录</strong></h4><h5 data-lines="1" data-sign="4f474aa0317f5fab32746ed19e8b0a08" id="五级目录"><a href="#五级目录" class="anchor"></a><strong>五级目录</strong></h5><h6 data-lines="1" data-sign="e54b3244c958b60083fd0fd1f0a79448" id="六级目录"><a href="#六级目录" class="anchor"></a><strong>六级目录</strong></h6><h1 data-lines="1" data-sign="b0d40797eed445eb5e29a011e7fe1e1f" id="一级目录-11"><a href="#一级目录-11" class="anchor"></a><strong>一级目录</strong></h1><h2 data-lines="1" data-sign="f055a5ed87cfe7934262638b5ea1e076" id="二级目录-8"><a href="#二级目录-8" class="anchor"></a><strong>二级目录</strong></h2><h3 data-lines="1" data-sign="5ed4b1b9e11053095450d803a66e8595" id="三级目录-11"><a href="#三级目录-11" class="anchor"></a><strong>三级目录</strong></h3><h4 data-lines="1" data-sign="6ac151d6f6d0a08da7eda2ff1b644280" id="四级目录-6"><a href="#四级目录-6" class="anchor"></a><strong>四级目录</strong></h4><h5 data-lines="1" data-sign="de2883023dc43dda752c05db796f8739" id="五级目录-3"><a href="#五级目录-3" class="anchor"></a><strong>五级目录</strong></h5><h6 data-lines="1" data-sign="71084d01bb4fa484cab4b75b7d531bd2" id="六级目录-3"><a href="#六级目录-3" class="anchor"></a><strong>六级目录</strong></h6></div>`
//获取当前dom应该向左边移动的位置
function getItemPaddingLeft(item){
let defaultVal = 16;
let maxTreeLevel =1;
return (defaultVal+16*(item.treeLevel-maxTreeLevel))+'px';
}
function getContentTree(contentHtmlStr) {
if(!contentHtmlStr) {
return [];
}
let hDomList = contentHtmlStr.match(
/<h[1-6]{1}[^>]*>([\s\S]*?)<\/h[1-6]{1}>/g
);
if(!Array.isArray(hDomList)){
return [];
}
const dirTree = [];
//最大的h标签值
let maxhLevel;
hDomList.forEach((hDom,index)=>{
const startIndex = hDom.indexOf("id=\"");
const endIndex = hDom.indexOf("\">");
if(startIndex!=-1 && endIndex!=-1){
const re = /<h([1-6]{1})[^>]*>([\s\S]*?)<\/h[1-6]{1}>/
let hLevel = Number(hDom.replace(re, "$1"));
const domId = hDom.slice(startIndex+4,endIndex);
let titleContent = hDom.replace(re, "$2");
// 有可能匹配到的是这样'我<strong>是</strong>h1<em>斜体</em>介绍' 里面还有标签(直接去掉标签)
let title = titleContent.replaceAll(/<([\s\S]*?)>|<\/([\s\S]*?)>/g, "");
const currentDir = {
id:(index+1),//用于点击的时候选中态
title,
domId,
hLevel,//h标签值(1,2,3,4,5,6)
isOpen:true
}
if(dirTree.length>0){
//查找最近的父类(将数组反转才能找到最近的)
//reverse会改变原数组,JSON.parse(JSON.stringify())方法去一下生成新数组
let reverseDirTree = JSON.parse(JSON.stringify(dirTree)).reverse();
let findParent = reverseDirTree.find(i=>currentDir.hLevel > i.hLevel);
if(findParent){
currentDir.pid = findParent.id;
}else{
currentDir.pid = 0;
}
}else{
currentDir.pid = 0;
}
if(maxhLevel){
if(currentDir.hLevel < maxhLevel){
maxhLevel = currentDir.hLevel;
}
}else{
maxhLevel = currentDir.hLevel;
}
dirTree.push(currentDir)
}
})
dirTree.forEach((item)=>{
item.parentTreeLevel = 0;//父类默认是0
item.treeLevel = (item.hLevel - maxhLevel) +1;//目录树层级,也代表当前h标签解析的内容在父类属于第几层级,1是最大层级
if(item.pid!==0){
const findParent = dirTree.find(i=>i.id === item.pid);
if(findParent){
if(!Array.isArray(findParent.children)){
findParent.children = []
}
item.parentTreeLevel = (findParent.hLevel - maxhLevel) +1;//父目录树层级,也代表当前h标签解析的内容在父类属于第几层级,1是最大层级
findParent.children.push(item)
}
}
})
return dirTree.filter((i)=>i.pid === 0);
}
let contentTree = getContentTree(contentHtmlStr);
let contentTreeHtml = '';
function calcContentTreeHtml(contentTree) {
if (contentTree.length > 0) {
contentTreeHtml +=`<div class="a-list">`
for (let i in contentTree) {
contentTreeHtml +=`
<div>
<div
style="padding-left:${getItemPaddingLeft(contentTree[i])}"
class="a-list-content"
>
<a class="${(Array.isArray(contentTree[i].children) && contentTree[i].children.length>0 && contentTree[i].isOpen)?'is-open':''} ${(Array.isArray(contentTree[i].children) && contentTree[i].children.length>0)?'has-children':''}">
<span class="space-inner" title="${contentTree[i].title}">${contentTree[i].title}</span>
</a>
</div>
`
if (Array.isArray(contentTree[i].children) && contentTree[i].children.length>0) {
calcContentTreeHtml(contentTree[i].children);
}
contentTreeHtml += `</div>`;
}
contentTreeHtml += `</div>`;
}
}
calcContentTreeHtml(contentTree);
document.getElementById('menuBox').innerHTML = contentTreeHtml;
</script>
</body>
</html>
五.总结
1.如果要转换成功react或者vue,只要把calcContentTreeHtml方法转换成组件就行
2.方法一和方法二比较主要是里面的dom摆放位置不一样,方法一是因为里面的div有继承关系所以getItemPaddingLeft方法计算方式繁琐一点,方法二是加了主要是在外层加了一个div,然后计算padding-left就简单一点,但方法二的样式还需改造,方法一和方法二的差别看getItemPaddingLeft和calcContentTreeHtml方法就行