Typecho插件實現添加文章目錄的方法詳解
我的長博文不少,比較影響閱讀體驗,有必要添加一個文章目錄功能。相比 WordPress, Typecho 的插件就比較少瞭。我想找一個像掘金那樣為文章添加目錄的插件,沒一個合適的。此類教程也不是很多,而且差不多都是前臺 JavaScript 來實現的,感覺這樣不如後臺實現來的好。
註意:我使用的是Joe主題7.3,其他主題文件路徑可能不一樣。
添加文章標題錨點
1.聲明 createAnchor 函數
在 core/functions.php
中添加如下代碼:
// 添加文章標題錨點 function createAnchor($obj) { global $catalog; global $catalog_count; $catalog = array(); $catalog_count = 0; $obj = preg_replace_callback('/<h([1-4])(.*?)>(.*?)<\/h\1>/i', function($obj) { global $catalog; global $catalog_count; $catalog_count ++; $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count); return '<h'.$obj[1].$obj[2].' id="cl-'.$catalog_count.'">'.$obj[3].'</h'.$obj[1].'>'; }, $obj); return $obj; }
也可以在標題元素內添加 <a>
標簽,然後該標簽新增 id
屬性。
createAnchor
函數主要是通過正則表達式替換文章標題H1~H4來添加錨點,接下來我們需要調用它。
2.調用函數
同樣在 core/core.php
中的 themeInit
方法最後一行之前添加如下代碼:
if ($self->is('single')) { $self->content = createAnchor($self->content); }
現在可以查看一下文章詳情頁面的源代碼。文章的 H1~H4
元素應該添加瞭諸如 cl-1
、cl-2
之類的 id
屬性值。具體啥名不是關鍵,好記就行。
顯示文章目錄
1.聲明 getCatalog 函數
在 core/functions.php
中添加如下代碼:
// 顯示文章目錄 function getCatalog() { global $catalog; $str = ''; if ($catalog) { $str = '<ul class="list">'."\n"; $prev_depth = ''; $to_depth = 0; foreach($catalog as $catalog_item) { $catalog_depth = $catalog_item['depth']; if ($prev_depth) { if ($catalog_depth == $prev_depth) { $str .= '</li>'."\n"; } elseif ($catalog_depth > $prev_depth) { $to_depth++; $str .= '<ul class="sub-list">'."\n"; } else { $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth; if ($to_depth2) { for ($i=0; $i<$to_depth2; $i++) { $str .= '</li>'."\n".'</ul>'."\n"; $to_depth--; } } $str .= '</li>'; } } $str .= '<li class="item"><a class="link" href="#cl-'.$catalog_item['count'].'" rel="external nofollow" title="'.$catalog_item['text'].'">'.$catalog_item['text'].'</a>'; $prev_depth = $catalog_item['depth']; } for ($i=0; $i<=$to_depth; $i++) { $str .= '</li>'."\n".'</ul>'."\n"; } $str = '<section class="toc">'."\n".'<div class="title">文章目錄</div>'."\n".$str.'</section>'."\n"; } echo $str; }
getCatalog
方法通過遞歸 $catalog
數組生成文章目錄,接下來我們需要調用它。
2.函數
最好將放在右側邊欄中。為此在 public/aside.php
中添加如下代碼:
<?php if ($this->is('post')) getCatalog(); ?>
註意:隻有文章才使用目錄,獨立頁面那些不需要,所以加瞭判斷。Typecho 有一些神奇的 is
語法可以方便二次開發,可以訪問它的官網文檔瞭解更多。
現在點擊右側的文章目錄,可以滾動到相應的文章小標題位置瞭。
添加文章目錄樣式
可以看到,當前的文章目錄還比較醜陋,我們來美化一下。在 assets/css/joe.post.min.scss
中添加如下 SCSS 代碼:
.joe_aside { .toc { position: sticky; top: 20px; width: 250px; background: var(--background); border-radius: var(--radius-wrap); box-shadow: var(--box-shadow); overflow: hidden; .title { display: block; border-bottom: 1px solid var(--classA); font-size: 16px; font-weight: 500; height: 45px; line-height: 45px; text-align: center; color: var(--theme); } .list { padding-top: 10px; padding-bottom: 10px; max-height: calc(100vh - 80px); overflow: auto; .link { display: block; padding: 8px 16px; border-left: 4px solid transparent; color: var(--main); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; &:hover { background-color: var(--classC); } &.active { border-left-color: var(--theme); } } } } }
為瞭方便操作,將 .toc
設置成 position: sticky;
實現瞭吸頂定位。考慮到文章目錄可能很多,為 .toc
列表添加瞭 overflow: auto;
,如代碼第 3 ~ 4
行。
由於 .joe_header
(主題標頭)也使用瞭吸頂定位,導致和文章目錄有遮擋,所有加瞭 has_toc .joe_header
來取消頁面主題標頭的吸頂功能,如下代碼:
.has_toc { .joe_header { position: relative; } }
定位到文章
要顯示文章目錄當前選中項的狀態,需要用到 JavaScript 給選中項添加一個 active
樣式。在 assets/js/joe.post_page.js
中添加如下代碼:
var headings = $('.joe_detail__article').find('h1, h2, h3, h4'); var links = $('.toc .link'); var tocList = document.querySelector('.tocr > .list'); var itemHeight = $('.toc .item').height(); var distance = tocList.scrollHeight - tocList.clientHeight; var timer = 0; // 是否自動滾動 var autoScrolling = true; function setItemActive(id) { links.removeClass('active'); var link = links.filter("[href='#" + id + "']") link.addClass('active'); } function onChange() { autoScrolling = true; if (location.hash) { id = location.hash.substr(1); var heading = headings.filter("[id='" + id + "']"); var top = heading.offset().top - 15; window.scrollTo({ top: top }) setItemActive(id) } } window.addEventListener('hashchange', onChange); // hash沒有改變時手動調用一次 onChange();
由於佈局和滾動動畫的影響,導致錨點定位有點偏差。我們再 setItemActive
函數中用 scrollTo
或 scrollIntoView
來糾正。另外,我們希望有錨點的鏈接可以直接定位,因此監聽瞭 hashchange
事件。點擊文章目錄測試一下定位,再手動鍵入錨點測試一下,應該都沒啥問題。
定位到目錄
目前可以從文章目錄定位到文章標題瞭,是單向定位,雙向定位還需要實現滾動文章內容時定位到文章目錄的當前項。正如我們馬上能想到的,需要監聽 window
的 scroll
事件,如下代碼:
function onScroll() { if (timer) { clearTimeout(timer); } timer = setTimeout(function () { var top = $(window).scrollTop(); var count = headings.length; for (var i = 0; i < count; i++) { var j = i; // 滾動和點擊時 index 相差 1,需要 autoScrolling 來區分 if (i > 0 && !autoScrolling) { j = i - 1; } var headingTop = $(headings[i]).offset().top; var listTop = distance * i / count // 判斷滾動條滾動距離是否大於當前滾動項可滾動距離 if (headingTop > top) { var id = $(headings[j]).attr('id'); setItemActive(id); // 如果目錄列表有滑條,使被選中的下一元素可見 if (listTop > 0) { // 向上滾動 if (listTop < itemHeight) { listTop -= itemHeight; } else { listTop += itemHeight; } $(tocList).scrollTop(listTop) } break; } else if (i === count - 1) { // 特殊處理最後一個元素 var id = $(headings[i]).attr('id'); setItemActive(id); if (listTop > 0) { $(tocList).scrollTop(distance) } } } autoScrolling = false; }, 100); } $(window).on('scroll', onScroll);
首先,在 onScroll
事件處理函數中遍歷標題數組 headings
, 如果滾動條滾動距離 top
大於當前標題項 item
可滾動距離 headingTop
,再調用 setItemActive
函數,傳入當前的標題項的 id
來判斷文章目錄激活狀態。
如果目錄列表有滑條,調用 jQuery 的 scrollTop
方法滾動目錄列表滑條,使被選中目錄項的上下元素可見,
現在文章目錄基本上可用瞭,也還美觀,後續可以考慮優化再封裝成一個插件。
吐槽一下:Joe 主題太依賴jQuery瞭,修改起來費勁 ::(汗)。
到此這篇關於Typecho插件實現添加文章目錄的方法詳解的文章就介紹到這瞭,更多相關Typecho添加文章目錄內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!