From 4460e55de210fd9a23a762fe76f5c32297d68d76 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Wed, 17 May 2023 13:43:35 +0200 Subject: Improve table of contents intersection observer (#64) --- .changeset/grumpy-trains-decide.md | 5 ++++ .../TableOfContents/MobileTableOfContents.astro | 2 +- .../components/TableOfContents/starlight-toc.ts | 30 +++++++++++++++++----- 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 .changeset/grumpy-trains-decide.md diff --git a/.changeset/grumpy-trains-decide.md b/.changeset/grumpy-trains-decide.md new file mode 100644 index 00000000..72ca0b6d --- /dev/null +++ b/.changeset/grumpy-trains-decide.md @@ -0,0 +1,5 @@ +--- +"@astrojs/starlight": patch +--- + +Fix table of contents intersection observer for all possible viewport sizes. diff --git a/packages/starlight/components/TableOfContents/MobileTableOfContents.astro b/packages/starlight/components/TableOfContents/MobileTableOfContents.astro index 9cb36c6e..14efd35c 100644 --- a/packages/starlight/components/TableOfContents/MobileTableOfContents.astro +++ b/packages/starlight/components/TableOfContents/MobileTableOfContents.astro @@ -121,7 +121,7 @@ const toc = generateToC(Astro.props.headings, config.tableOfContents); } constructor() { - super(); + super({ smallViewport: true }); const details = this.querySelector('details'); if (!details) return; const closeToC = () => { diff --git a/packages/starlight/components/TableOfContents/starlight-toc.ts b/packages/starlight/components/TableOfContents/starlight-toc.ts index fd194450..5320b8d8 100644 --- a/packages/starlight/components/TableOfContents/starlight-toc.ts +++ b/packages/starlight/components/TableOfContents/starlight-toc.ts @@ -12,7 +12,7 @@ export class StarlightTOC extends HTMLElement { this._current = link; } - constructor() { + constructor({ smallViewport = false } = {}) { super(); /** All the links in the table of contents. */ @@ -68,17 +68,35 @@ export class StarlightTOC extends HTMLElement { } }; - const headingsObserver = new IntersectionObserver(setCurrent, { - rootMargin: '-10% 0% -85%', - }); - // Observe elements with an `id` (most likely headings) and their siblings. // Also observe direct children of `.content` to include elements before // the first heading. const toObserve = document.querySelectorAll( 'main [id], main [id] ~ *, main .content > *' ); - toObserve.forEach((h) => headingsObserver.observe(h)); + /** Start intersections at nav height + 2rem padding. */ + const top = (smallViewport ? 104 : 64) + 32; + /** End intersections 1.5rem later. */ + const bottom = top + 24; + + let observer: IntersectionObserver | undefined; + function observe() { + if (observer) observer.disconnect(); + const height = document.documentElement.clientHeight; + const rootMargin = `-${top}px 0% ${bottom - height}px`; + observer = new IntersectionObserver(setCurrent, { rootMargin }); + toObserve.forEach((h) => observer!.observe(h)); + } + observe(); + + const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1)); + let timeout: NodeJS.Timeout; + window.addEventListener('resize', () => { + // Disable intersection observer while window is resizing. + if (observer) observer.disconnect(); + clearTimeout(timeout); + timeout = setTimeout(() => onIdle(observe), 200); + }); } } -- cgit