toc.ts
1 const tocLinks = document.querySelectorAll<HTMLAnchorElement>(".toc-li a"); 2 const headings = document.querySelectorAll<HTMLElement>( 3 "#_top, article h1, article h2, article h3, article h4", 4 ); 5 const tocMap = new Map<Element, HTMLElement>(); 6 7 // Map TOC links to their corresponding headings 8 for (const link of tocLinks) { 9 const id = link.href.split("#")[1]; 10 const heading = document.getElementById(id); 11 if (heading) tocMap.set(heading, link as HTMLElement); 12 } 13 14 function checkVisibility(el: HTMLElement) { 15 const rect = el.getBoundingClientRect(); 16 const viewHeight = Math.max( 17 document.documentElement.clientHeight, 18 window.innerHeight, 19 ); 20 return !(rect.bottom < 0 || rect.top - viewHeight >= 0); 21 } 22 23 const observer = new IntersectionObserver( 24 () => { 25 let visible: HTMLElement | null = null; 26 27 for (const heading of headings) { 28 // check if this specific heading is visible on the screen 29 const isVisible = checkVisibility(heading); 30 31 const link = tocMap.get(heading); 32 33 if (!isVisible) { 34 continue; 35 } 36 37 if (link) link.classList.add("active"); 38 39 if (!visible) { 40 visible = heading; 41 } 42 43 break; 44 } 45 46 if (visible) { 47 for (const key of tocMap.keys()) { 48 if (key !== visible) { 49 const link = tocMap.get(key); 50 if (link) link.classList.remove("active"); 51 } 52 } 53 } 54 }, 55 { threshold: 0, root: null, rootMargin: "0px" }, 56 ); 57 58 // Observe all headings 59 for (const heading of headings) { 60 observer.observe(heading); 61 }