解析html内容的h标签成目录树(markdown解析出来的html)

一.本人用的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方法就行

相关推荐
As977_4 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu10830189116 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾7 分钟前
前端基础-html-注册界面
前端·算法·html
我要洋人死3 小时前
导航栏及下拉菜单的实现
前端·css·css3
小白白一枚11114 小时前
css实现div被图片撑开
前端·css
@蒙面大虾14 小时前
CSS综合练习——懒羊羊网页设计
前端·css
顾菁寒15 小时前
WEB第二次作业
前端·css·html
Qhumaing15 小时前
html第一个网页
网络·html·html5
荆州克莱17 小时前
[FE] React 初窥门径(四):React 组件的加载过程(render 阶段)
spring boot·spring·spring cloud·css3·技术
前端Hardy20 小时前
HTML&CSS:爱上班的猫咪
前端·javascript·css·vue.js·html