將Hexo Light主題的文章目錄改為折疊式TOC
注意
本文由 Gemini 3 Pro 語言模型產生。
前言
原本 Hexo Theme Light 的 TOC(目錄)在手機版排版不佳,在電腦版若使用懸浮定位又容易遮擋內容。為了徹底解決這個問題,我們決定採用「全平台統一」的設計:將目錄製作成一個可折疊的卡片區塊,嵌入在文章開頭。此版本特別針對 CSS 動畫的「幽靈高度」延遲問題進行優化,透過 JavaScript 動態計算高度,讓收合動作達到零延遲的滑順感。
修改步驟
1. 修改模板檔案
編輯 themes/light/layout/_partial/article.ejs。
將原本輸出 TOC 的位置替換為以下內容。
程式碼重點說明:
- 使用
scrollHeight取得目錄的精確高度,解決 CSSmax-height設太大導致收合時會有延遲感的問題。 - 使用
void body.offsetHeight強制瀏覽器重繪(Reflow),確保動畫能正確觸發。 - 利用
setTimeout配合 CSS 動畫時間(0.3s)切換按鈕文字。
<% if (!index && item.toc){ %>
<div id="unified-toc" class="toc-card">
<div class="toc-header" onclick="toggleToc()">
<span class="toc-title-text">📑 文章目錄</span>
<span class="toc-icon">▼</span>
</div>
<div class="toc-body">
<%- toc(item.content) %>
</div>
<div class="toc-footer" onclick="toggleToc()">
<span id="toc-btn-text">🔽 展開完整目錄</span>
</div>
</div>
<script>
function toggleToc() {
var card = document.getElementById('unified-toc');
var body = card.querySelector('.toc-body');
var btnText = document.getElementById('toc-btn-text');
var isExpanded = card.classList.contains('is-expanded');
if (isExpanded) {
// =========================================
// 準備收合 (Collapse)
// =========================================
// 1. 鎖定目前高度,防止動畫延遲
body.style.maxHeight = body.scrollHeight + "px";
void body.offsetHeight; // 強制重繪
// 【新增優化】解決長目錄收合時的視窗跳動問題
// 如果目錄頂部已經跑出螢幕上方 (top < 0),收合時自動滾回去
if (card.getBoundingClientRect().top < 0) {
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// 2. 移除 class 開始縮小
card.classList.remove('is-expanded');
// 3. 設定高度為 null,讓 CSS 接手縮回預設值
body.style.maxHeight = null;
// 4. 延遲切換文字 (300ms)
setTimeout(function() {
btnText.innerText = "🔽 展開完整目錄";
}, 300);
} else {
// =========================================
// 準備展開 (Expand)
// =========================================
card.classList.add('is-expanded');
// 計算真實高度並展開
body.style.maxHeight = body.scrollHeight + "px";
// 立即切換文字
btnText.innerText = "🔼 收合目錄";
}
}
</script>
<% } %>
2. 修改樣式檔案
編輯 themes/light/source/css/_partial/article.styl。
刪除舊的 TOC 樣式,加入以下 CSS。
樣式重點說明:
.toc-body:將transition設為0.3s ease-out,ease-out曲線能讓收合時的感覺更靈敏。.is-expanded:移除了寫死的max-height: 2000px,改由 JS 動態控制。- 漸層遮罩:使用
linear-gradient製作底部的淡出效果,展開後自動隱藏。
/* =========================================
全平台統一 TOC 樣式 (Unified TOC)
========================================= */
.toc-card
background #f8f9fa
border 1px solid #e9ecef
border-radius 8px
margin 20px 0 20px 0
box-shadow none /* 強制移除陰影,確保與提示區塊一樣扁平 */
padding 0
width 100%
overflow hidden
position relative
transition all 0.3s ease
/* --- 標題區域 --- */
.toc-header
padding 12px 16px
cursor pointer
display flex
justify-content space-between
align-items center
background #f1f3f5
border-bottom 1px solid #e9ecef
user-select none
transition background 0.2s
&:hover
background #e9ecef
.toc-title-text
font-weight bold
color #333
font-size 1.1em
.toc-icon
color #666
font-size 0.8em
transition transform 0.3s
/* --- 內容區域 (預設折疊) --- */
.toc-body
padding 0 16px
max-height 160px /* 預設顯示高度 */
overflow hidden
position relative
/* 使用 ease-out 讓收合手感更靈敏 */
transition max-height 0.3s ease-out
/* 漸層遮罩 */
&:after
content ""
position absolute
bottom 0
left 0
width 100%
height 50px
background linear-gradient(to bottom, rgba(248,249,250,0), rgba(248,249,250,1))
pointer-events none
transition opacity 0.3s
.toc
margin 12px 0
padding 0
li
list-style none
line-height 1.8em
font-size 0.95em
margin-bottom 4px
a
text-decoration none
color #555
@media (hover: hover)
&:hover
color #258fb8
&:active
color #258fb8
.toc-child
margin-left 1.2em
/* --- 底部按鈕區域 --- */
.toc-footer
text-align center
padding 10px
cursor pointer
border-top 1px solid #e9ecef
background #fff
color #258fb8
font-weight bold
font-size 0.9em
&:hover
background #f8f9fa
/* =========================================
展開狀態 (is-expanded)
========================================= */
.toc-card.is-expanded
.toc-icon
transform rotate(180deg)
.toc-body
&:after
opacity 0
效果預覽
完成修改後,文章目錄具備以下特性:
- 全平台統一:手機與電腦版面一致,簡化維護。
- 防遮擋:目錄嵌入文章流中,不會遮擋內文。
- 極速響應:展開與收合完全貼合內容高度,沒有「頓一下」的延遲感。
- 視覺優化:按鈕文字與動畫節奏精準同步,操作體驗流暢。
效果如下:
