將Hexo Light主題的文章目錄改為折疊式TOC

注意

本文由 Gemini 3 Pro 語言模型產生。

前言

原本 Hexo Theme Light 的 TOC(目錄)在手機版排版不佳,在電腦版若使用懸浮定位又容易遮擋內容。為了徹底解決這個問題,我們決定採用「全平台統一」的設計:將目錄製作成一個可折疊的卡片區塊,嵌入在文章開頭。此版本特別針對 CSS 動畫的「幽靈高度」延遲問題進行優化,透過 JavaScript 動態計算高度,讓收合動作達到零延遲的滑順感。

修改步驟

1. 修改模板檔案

編輯 themes/light/layout/_partial/article.ejs
將原本輸出 TOC 的位置替換為以下內容。

程式碼重點說明:

  • 使用 scrollHeight 取得目錄的精確高度,解決 CSS max-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-outease-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

效果預覽

完成修改後,文章目錄具備以下特性:

  • 全平台統一:手機與電腦版面一致,簡化維護。
  • 防遮擋:目錄嵌入文章流中,不會遮擋內文。
  • 極速響應:展開與收合完全貼合內容高度,沒有「頓一下」的延遲感。
  • 視覺優化:按鈕文字與動畫節奏精準同步,操作體驗流暢。

效果如下:
目錄收合

目錄展開