個人博客網站文章添加目錄導航


文章出自個人博客https://knightyun.github.io/2020/01/14/website-add-category,轉載請申明


概述

之前有寫文章探索如何給個人博客網站添加文章搜索功能,可以方便的通過關鍵詞檢索相關文章,現在再來探索一下另一個功能,即給文章添加目錄導航;對於篇幅較短的文章,目錄的有無影響不大,但是當文章篇幅過長時,一個能提供預覽和跳轉的目錄結構預覽,就顯得意義重大了,接下來就來一步步將它實現出來;

原理

樣式

實現功能必先思考其原理,目錄預覽其實就是一塊內容,包含當前頁面不同級別的標題的組合,並結構化的展示出來,首先我們可以參考一些網站的做法,比如 CSDN 的博客文章就有配置目錄插件,下面就是我的某篇文章的目錄預覽圖:

category.png

它們的目錄插件就是右邊側欄的一個按鈕,鼠標放上去就會顯示一個側欄,內容就是當前文章的小標題的集合,不同級別的標題對應這不同程度的縮進,並且點擊每個標題都會有相應的頁面跳轉,這也基本是我們常見而熟悉的目錄形式,那麼我們就以此爲參考來實現;

目錄獲取

想要生成這麼一個目錄之前,當然是要先獲取目錄的內容,前面講過,目錄的內容就是當前文章的所有標題的集合,而我們知道,在 HTML 中標題相關的標籤是 h1, h2, h3, h4, h5, h6 這幾個,所以直接獲取它們就行了,比如:

// 獲取所有的標籤名爲 h1 的元素
document.querySelectorAll('h1');
// 獲取所有的標籤名爲 h1 - h6 的元素
document.querySelectorAll('h1, h2, h3, h4, h5, h6');

獲取內容是一個數組,包含所有標題節點;接下來的問題就是考慮如何結構化存儲,這樣便於理解的同時又方便後期的讀取,所謂結構化,即目錄本身就是一類 結構,比如,目錄包含多個一級標題,同時某些以及標題可能還有多個二級標題,甚至再向下延伸出三級標題等等,類似下面的結構:

├─ 一級標題 1
│  └─ 二級標題 1
│      └─ 三級標題 1
│          └─ 四級標題
├─ 一級標題 2
│  └─ 二級標題 1
├─ 一級標題 3
│  ├─ 二級標題 1
│  ├─ 二級標題 2
│  ├─ 二級標題 3
│  │  ├─ 三級標題 1
│  │  ├─ 三級標題 2
... ... 

理論的做法就是以樹結構保存獲取的標題,類似於下面這種:

[{
    node: 'h1Node', // 一級標題 1 對應的節點
    child: [{
        node: 'h2Node', // 二級標題 1
        child: [{
            node: 'h3Node', // 三級標題 1
            child: []
        }]
    },
    {
        node: 'h2Node', // 二級標題 2
        child: []
    }]
},
{
    node: 'h1Node', // 一級標題 2
    child: [{
        node: 'h2Node', // 二級標題 1
        child: []
    }]
},
{
    node: 'h1Node', // 一級標題 3
    child: []
}]

看着還是比較複雜的,耗費的空間也較大,需要遞歸式獲取,最後也要遞歸式的輸出,一般文章目錄包含的標題數量也是較少的,所以暫且不用這種結構來保存,可以換一種簡單的思路,即我們最後生成該目錄時可以選擇一行一行遞進的輸出,即設計如下結構:

[
    'h1Node', // 一級標題 1 對應的元素節點
    'h2Node', // 二級標題 1 (隸屬於一級標題 1)
    'h3Node', // 三級標題 1 (隸屬於二級標題 1)
    'h2Node', // 二級標題 2 (隸屬於一級標題 1)
    'h1Node', // 一級標題 2
    'h2Node', // 二級標題 1 (隸屬於一級標題 2)
    'h1Node'  // 一級標題 3
]

因爲我們只要求最終能輸出一列格式化的目錄,即挨個依次輸出,所以只需以此存儲即可,這樣佔用的空間和複雜度都有所減少;

目錄生成

最終展示效果設定爲最上面的樣圖所示,按照之前設計的存儲結構,遍歷該數組一行一行打印出來即可;關於不用級別的標題應用不同程度的縮進,可以巧妙利用一下元素節點的 nodeName 這個屬性,比如元素節點 <h1></h1> 對應的 nodeName 就是 H1h2 就對應 H2,以此類推,我們就利用該值最後的那個數字,乘以一個固定縮進值,這樣級別遞增的標題節點也就擁有了遞增的縮進值,最後樣式部分就可以利用 padding-left 來實現縮進,js 代碼的實現思路如下:

// node 爲標題節點,32 是標題級別增加而多縮進的值
node.style.paddingLeft = node.nodeName.slice(-1) * 32 + 'px';

對於點擊標題跳轉到文章對應標題所在位置這個功能,實現也比較簡單,設置對應 錨點 即可,也就是標題元素需要設置一個 id 屬性值,然後給點擊的 <a> 標籤的 href 屬性也設置爲這個 id 值即可,例如:

<h1 id="my-h1">一級標題</h1>

<a href="my-h1">點我跳轉到一級標題</a>

具體實現

第三方庫

避免重複造輪子,一些基礎的格式化樣式就交給第三方庫去解決吧,這裏使用的是 Materialize 這個庫,安裝和引用教程去官網:https://materializecss.com 查看;

HTML部分

具體參考代碼與說明如下:

<!-- 引用第三方庫 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

<!-- 固定於屏幕右下方的一個懸浮按鈕 -->
<div class="fixed-action-btn">
    <a class="btn-floating btn-large blue z-depth-4">
        <i class="large material-icons">apps</i>
    </a>
    <ul>
        <!-- 文章目錄按鈕 -->
        <li class="category-btn hide">
            <a class="sidenav-trigger btn-floating blue lighten-2" data-target="category">
                <i class="material-icons">format_list_bulleted</i>
            </a>
        </li>
        <!-- 下面也可以添加其他按鈕,如返回文章頂部等-->
        <li>
            <a class="btn-floating blue lighten-2" href="javascript: scrollTo(0, 0);">
                <i class="material-icons">publish</i>
            </a>
        </li>
    </ul>
</div>

<!-- 文章目錄側欄 -->
<ul id="category" class="hide sidenav grey lighten-4 grey-text text-darken-3">
    <li><p class="center-align">目錄</p></li>
</ul>

<!-- 下面的元素中存放文章內容 -->
<div id="post-content">
    <!-- 文章內容,需要注意的只有,爲不同的標題元素設置不同的 id 屬性以實現跳轉 -->
    <!-- 以下爲示例內容 -->
    <h1 id="t1">Title 1</h1>
    <p>Hello World!</p>
    <p>Hello World!</p>
    
    <h2 id="t11">Title 1</h2>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    
    <h2 id="t12">Title 1</h2>
    <h3 id="t121">Title 1</h3>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    
    <h1 id="t2">Title 2</h1>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    
    <h1 id="t3">Title 3</h1>
    <p>Hello World!</p>
    
    <h2 id="t31">Title 3</h2>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
    <p>Hello World!</p>
</div>

CSS部分

樣式部分因人而異,可以自行設計調整,以下爲參考:

#category li a:before { /* 添加一個摺疊符號,爲了好看 */
    content: "∟";
    position: absolute;
    left: 10px;
    bottom: 5px;
    font-size: 12px;
}

JavaScript部分

該部分就是核心所在了,對應上面的 HTML 和 CSS 部分,實現如下:

// 初始化第三方庫的插件
M.AutoInit(); 
document.addEventListener('DOMContentLoaded', function () {
    var elemCategory = document.querySelector('#category');
    M.Sidenav.init(elemCategory, {
        'edge': 'right' // right 表示在右側欄顯示,left 則表示在左邊顯示
    });
});

var postContent = document.querySelector('#post-content');

if (postContent) { // 存在文章內容
    var categories = postContent.querySelectorAll('h1, h2, h3, h4, h5, h6');

    if (categories.length > 0) { // 文章存在標題
        var category = document.querySelector('#category'),
            categoryBtn = document.querySelector('.category-btn');
        var li = document.createElement('li'),
            a = document.createElement('a');
        
        a.className = 'waves-effect';
        // 存在目錄則顯示目錄按鈕和側欄
        category.classList.remove('hide');
        categoryBtn.classList.remove('hide');
    
        categories.forEach(node => {
            // 每次 cloneNode 取代 createElement
            // 因爲克隆一個元素快於創建一個元素
            var _li = li.cloneNode(false),
                _a = a.cloneNode(false);
    
            _a.innerText = node.innerText;
            // 爲標題設置跳轉鏈接
            _a.href = '#' + node.id;
            _li.appendChild(_a);
            // 爲不同級別標題應用不同的縮進
            _li.style.paddingLeft = node.nodeName.slice(-1) * 32 + 'px';
            category.appendChild(_li);
        })
    }
}

效果

最後附上幾張本人博客網站實現的最終效果圖,也歡迎點擊 https://knightyun.github.io 前往訪問 ^_^

批註 2020-01-14 154351.png

批註 2020-01-14 154311.png

批註 2020-01-14 154435.png


技術文章推送
手機、電腦實用軟件分享
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章