From 452039deb5db4fa60c2447db723df79af6240f1f Mon Sep 17 00:00:00 2001 From: foxton9 Date: Sun, 8 Dec 2024 23:28:10 +0800 Subject: [PATCH] revise TOC logic fix bugs in fallback, markVisibleSection; refine rAF and connectedCb --- src/components/widget/TOC.astro | 76 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/components/widget/TOC.astro b/src/components/widget/TOC.astro index c33e8dabe..8ac3e5aff 100644 --- a/src/components/widget/TOC.astro +++ b/src/components/widget/TOC.astro @@ -75,30 +75,22 @@ class TableOfContents extends HTMLElement { this.observer = new IntersectionObserver( this.markVisibleSection, { threshold: 0 } ); - } + }; markVisibleSection = (entries: IntersectionObserverEntry[]) => { - requestAnimationFrame(() => { - entries.forEach((entry) => { - const id = entry.target.children[0]?.getAttribute("id"); - - const idx = id ? this.headingIdxMap.get(id) : undefined; + entries.forEach((entry) => { + const id = entry.target.children[0]?.getAttribute("id"); + const idx = id ? this.headingIdxMap.get(id) : undefined; + if (idx != undefined) + this.active[idx] = entry.isIntersecting; - if (entry.isIntersecting && this.anchorNavTarget == entry.target) - this.anchorNavTarget = null; - - if (idx != undefined) - this.active[idx] = entry.isIntersecting; - }); - - requestAnimationFrame(() => { - if (!document.querySelector(`#toc .${this.visibleClass}`)) { - this.fallback(); - } - this.toggleActiveHeading(); - this.scrollToActiveHeading(); - }); + if (entry.isIntersecting && this.anchorNavTarget == entry.target.firstChild) + this.anchorNavTarget = null; }); + + if (!this.active.includes(true)) + this.fallback(); + this.update(); }; toggleActiveHeading = () => { @@ -132,7 +124,7 @@ class TableOfContents extends HTMLElement { if (this.anchorNavTarget || !this.tocEl) return; const activeHeading = - document.querySelectorAll("#toc .visible"); + document.querySelectorAll(`#toc .${this.visibleClass}`); if (!activeHeading.length) return; const topmost = activeHeading[0]; @@ -153,6 +145,15 @@ class TableOfContents extends HTMLElement { }); }; + update = () => { + requestAnimationFrame(() => { + this.toggleActiveHeading(); + // requestAnimationFrame(() => { + this.scrollToActiveHeading(); + // }); + }); + }; + fallback = () => { if (!this.sections.length) return; @@ -162,20 +163,16 @@ class TableOfContents extends HTMLElement { if (this.isInRange(offsetTop, 0, window.innerHeight) || this.isInRange(offsetBottom, 0, window.innerHeight) - || (offsetTop < 0 && offsetBottom > window.innerHeight)) { + || (offsetTop < 0 && offsetBottom > window.innerHeight)) { this.markActiveHeading(i); } - else break; + else if (offsetTop > window.innerHeight) break; } - - requestAnimationFrame(() => { - this.toggleActiveHeading(); - }) }; markActiveHeading = (idx: number)=> { this.active[idx] = true; - } + }; handleAnchorClick = (event: Event) => { const anchor = event @@ -195,14 +192,19 @@ class TableOfContents extends HTMLElement { isInRange(value: number, min: number, max: number) { return min < value && value < max; - } + }; connectedCallback() { // wait for the onload animation to finish, which makes the `getBoundingClientRect` return correct values - setTimeout(() => { - this.init(); - }, 250); - } + const element = document.querySelector('.prose'); + if (element) { + element.addEventListener('animationend', () => { + this.init(); + }, { once: true }); + } else { + console.warn('Animation element not found'); + } + }; init() { this.tocEl = document.getElementById( @@ -221,6 +223,8 @@ class TableOfContents extends HTMLElement { document.querySelectorAll("#toc a[href^='#']") ); + if (this.tocEntries.length === 0) return; + this.sections = new Array(this.tocEntries.length); this.headings = new Array(this.tocEntries.length); for (let i = 0; i < this.tocEntries.length; i++) { @@ -240,8 +244,8 @@ class TableOfContents extends HTMLElement { ); this.fallback(); - this.scrollToActiveHeading(); - } + this.update(); + }; disconnectedCallback() { this.sections.forEach((section) => @@ -249,7 +253,7 @@ class TableOfContents extends HTMLElement { ); this.observer.disconnect(); this.tocEl?.removeEventListener("click", this.handleAnchorClick); - } + }; } customElements.define("table-of-contents", TableOfContents);