Typecho插件實(shí)現(xiàn)添加文章目錄的方法詳解
目錄
- 添加文章標(biāo)題錨點(diǎn)
- 顯示文章目錄
- 添加文章目錄樣式
- 定位到文章
- 定位到目錄
我的長(zhǎng)博文不少,比較影響閱讀體驗(yàn),有必要添加一個(gè)文章目錄功能。相比 Wordpress, Typecho 的插件就比較少了。我想找一個(gè)像掘金那樣為文章添加目錄的插件,沒一個(gè)合適的。此類教程也不是很多,而且差不多都是前臺(tái) JavaScript 來(lái)實(shí)現(xiàn)的,感覺這樣不如后臺(tái)實(shí)現(xiàn)來(lái)的好。
注意:我使用的是Joe主題7.3,其他主題文件路徑可能不一樣。
添加文章標(biāo)題錨點(diǎn)
1.聲明 createAnchor 函數(shù)
在 core/functions.php
中添加如下代碼:
// 添加文章標(biāo)題錨點(diǎn) 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; }
也可以在標(biāo)題元素內(nèi)添加 <a>
標(biāo)簽,然后該標(biāo)簽新增 id
屬性。
createAnchor
函數(shù)主要是通過(guò)正則表達(dá)式替換文章標(biāo)題H1~H4來(lái)添加錨點(diǎn),接下來(lái)我們需要調(diào)用它。
2.調(diào)用函數(shù)
同樣在 core/core.php
中的 themeInit
方法最后一行之前添加如下代碼:
if ($self->is("single")) { $self->content = createAnchor($self->content); }
現(xiàn)在可以查看一下文章詳情頁(yè)面的源代碼。文章的 H1~H4
元素應(yīng)該添加了諸如 cl-1
、cl-2
之類的 id
屬性值。具體啥名不是關(guān)鍵,好記就行。
顯示文章目錄
1.聲明 getCatalog 函數(shù)
在 core/functions.php
中添加如下代碼:
// 顯示文章目錄 function getCatalog() { global $catalog; $str = ""; if ($catalog) { $str = "<ul>"."\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>"."\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><a 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>"."\n"."<div>文章目錄</div>"."\n".$str."</section>"."\n"; } echo $str; }
getCatalog
方法通過(guò)遞歸 $catalog
數(shù)組生成文章目錄,接下來(lái)我們需要調(diào)用它。
2.函數(shù)
最好將放在右側(cè)邊欄中。為此在 public/aside.php
中添加如下代碼:
<?php if ($this->is("post")) getCatalog(); ?>
注意:只有文章才使用目錄,獨(dú)立頁(yè)面那些不需要,所以加了判斷。Typecho 有一些神奇的 is
語(yǔ)法可以方便二次開發(fā),可以訪問(wèn)它的官網(wǎng)文檔了解更多。
現(xiàn)在點(diǎn)擊右側(cè)的文章目錄,可以滾動(dòng)到相應(yīng)的文章小標(biāo)題位置了。
添加文章目錄樣式
可以看到,當(dāng)前的文章目錄還比較丑陋,我們來(lái)美化一下。在 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
設(shè)置成 position: sticky;
實(shí)現(xiàn)了吸頂定位。考慮到文章目錄可能很多,為 .toc
列表添加了 overflow: auto;
,如代碼第 3 ~ 4
行。
由于 .joe_header
(主題標(biāo)頭)也使用了吸頂定位,導(dǎo)致和文章目錄有遮擋,所有加了 has_toc .joe_header
來(lái)取消頁(yè)面主題標(biāo)頭的吸頂功能,如下代碼:
.has_toc { .joe_header { position: relative; } }
定位到文章
要顯示文章目錄當(dāng)前選中項(xiàng)的狀態(tài),需要用到 JavaScript 給選中項(xiàng)添加一個(gè) 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; // 是否自動(dòng)滾動(dòng) 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沒有改變時(shí)手動(dòng)調(diào)用一次 onChange();
由于布局和滾動(dòng)動(dòng)畫的影響,導(dǎo)致錨點(diǎn)定位有點(diǎn)偏差。我們?cè)?setItemActive
函數(shù)中用 scrollTo
或 scrollIntoView
來(lái)糾正。另外,我們希望有錨點(diǎn)的鏈接可以直接定位,因此監(jiān)聽了 hashchange
事件。點(diǎn)擊文章目錄測(cè)試一下定位,再手動(dòng)鍵入錨點(diǎn)測(cè)試一下,應(yīng)該都沒啥問(wèn)題。
定位到目錄
目前可以從文章目錄定位到文章標(biāo)題了,是單向定位,雙向定位還需要實(shí)現(xiàn)滾動(dòng)文章內(nèi)容時(shí)定位到文章目錄的當(dāng)前項(xiàng)。正如我們馬上能想到的,需要監(jiān)聽 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; // 滾動(dòng)和點(diǎn)擊時(shí) index 相差 1,需要 autoScrolling 來(lái)區(qū)分 if (i > 0 && !autoScrolling) { j = i - 1; } var headingTop = $(headings[i]).offset().top; var listTop = distance * i / count // 判斷滾動(dòng)條滾動(dòng)距離是否大于當(dāng)前滾動(dòng)項(xiàng)可滾動(dòng)距離 if (headingTop > top) { var id = $(headings[j]).attr("id"); setItemActive(id); // 如果目錄列表有滑條,使被選中的下一元素可見 if (listTop > 0) { // 向上滾動(dòng) if (listTop < itemHeight) { listTop -= itemHeight; } else { listTop += itemHeight; } $(tocList).scrollTop(listTop) } break; } else if (i === count - 1) { // 特殊處理最后一個(gè)元素 var id = $(headings[i]).attr("id"); setItemActive(id); if (listTop > 0) { $(tocList).scrollTop(distance) } } } autoScrolling = false; }, 100); } $(window).on("scroll", onScroll);
首先,在 onScroll
事件處理函數(shù)中遍歷標(biāo)題數(shù)組 headings
, 如果滾動(dòng)條滾動(dòng)距離 top
大于當(dāng)前標(biāo)題項(xiàng) item
可滾動(dòng)距離 headingTop
,再調(diào)用 setItemActive
函數(shù),傳入當(dāng)前的標(biāo)題項(xiàng)的 id
來(lái)判斷文章目錄激活狀態(tài)。
如果目錄列表有滑條,調(diào)用 jQuery 的 scrollTop
方法滾動(dòng)目錄列表滑條,使被選中目錄項(xiàng)的上下元素可見,
現(xiàn)在文章目錄基本上可用了,也還美觀,后續(xù)可以考慮優(yōu)化再封裝成一個(gè)插件。
吐槽一下:Joe 主題太依賴jQuery了,修改起來(lái)費(fèi)勁 ::(汗)。
到此這篇關(guān)于Typecho插件實(shí)現(xiàn)添加文章目錄的方法詳解的文章就介紹到這了,更多相關(guān)Typecho添加文章目錄內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
