解析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方法就行

相关推荐
刻刻帝的海角6 小时前
CSS 颜色
前端·css
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5
浪浪山小白兔17 小时前
HTML5 常用事件详解
前端·html·html5
陈钇钇19 小时前
持续升级《在线写python》小程序的功能,文章页增加一键复制功能,并自动去掉html标签
python·小程序·html
荆州克莱20 小时前
Golang的图形编程基础
spring boot·spring·spring cloud·css3·技术
python算法(魔法师版)21 小时前
html,css,js的粒子效果
javascript·css·html
LBJ辉1 天前
1. 小众但非常实用的 CSS 属性
前端·css
浪浪山小白兔1 天前
HTML 表单和输入标签详解
前端·html
荆州克莱1 天前
Golang的网络编程安全
spring boot·spring·spring cloud·css3·技术
陈奕迅本讯1 天前
HTML5和CSS3拔高
前端·css3·html5