程式碼走讀:目錄高亮,與一個永遠不亮的最後一章
筆記頁右欄的目錄會跟著捲動高亮當前章節。第一版用 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 資料是理想化的,真實內容才是規格——章節可以很短、標題可以很長、引用可以很多,版面與互動的假設要拿真資料驗過才算數。
讀到這裡若有一點收穫,替這顆種子澆一次水——匿名、一人一次。