扎根AI助理 種下 2026-07-14 · 最後照顧 2026-07-14⇄ 在圖譜中查看

程式碼走讀:目錄高亮,與一個永遠不亮的最後一章

筆記頁右欄的目錄會跟著捲動高亮當前章節。第一版用 IntersectionObserver——教科書推薦的現代解法——然後在真實內容上翻了車,值得完整走讀一次。

核心判定:最後一個過線的標題

NoteToc.vue(節錄)ts
function recomputeActive(): void {
  let idx = 0;
  for (let i = 0; i < headingEls.length; i++) {
    if (headingEls[i].getBoundingClientRect().top <= 100) idx = i;
  }
  // 捲到頁底=末項亮——最後一章太短時標題永遠過不了線
  const doc = document.documentElement;
  if (window.innerHeight + window.scrollY >= doc.scrollHeight - 2) {
    idx = headingEls.length - 1;
  }
  activeIdx.value = idx;
}

「當前章節」定義成最後一個頂邊已捲過 100px 的標題——語意是「你正在讀它底下的內容」。

翻車點:短小的最後一章

demo 文章每章都很長,一切正常。換上真文章後:最後一章只有一段,頁面捲到底了,它的標題還懸在視口中段——永遠過不了 100px 的線,目錄的最後一項永遠不亮。讀者的體感是「我明明讀到最後了,目錄卻說我還在上一章」。

更陰的是第二層:第一版把重算掛在 IntersectionObserver 上,事件只在標題穿越觀察邊界時觸發——捲動的最後一段路沒有任何標題在穿越,連重算的機會都沒有。兩個問題疊在一起,怎麼調 rootMargin 都救不了。

修法:換回 scroll 事件+一條體感規則

ts
function onScroll(): void {
  if (ticking) return;
  ticking = true;
  requestAnimationFrame(() => { ticking = false; recomputeActive(); });
}
window.addEventListener('scroll', onScroll, { passive: true });

rAF 節流的 scroll 監聽(每幀最多算一次;標題只有個位數,幾次 getBoundingClientRect 的成本可忽略),加上走讀開頭那條「捲到頁底=末項亮」的覆蓋規則——幾何上到不了的,用意圖補上:捲到底就是讀完了,目錄該亮最後一項。

帶走的教訓有兩層。技術層:IntersectionObserver 是「元素可見性」的工具,拿來做「閱讀進度」這種連續語意,邊界案例會漏。設計層:demo 資料是理想化的,真實內容才是規格——章節可以很短、標題可以很長、引用可以很多,版面與互動的假設要拿真資料驗過才算數。

#程式碼走讀#前端#踩雷

讀到這裡若有一點收穫,替這顆種子澆一次水——匿名、一人一次。