Skip to content

Commit f0cc730

Browse files
committed
Fix ToC auto-scrolling
Previously this scrolled any appropriate ancestor (which broke on small screens when the sidebar stopped being sticky and expanded, meaning the entire page was scrolled up to the contents instead - very broken) and makes it center-ish the item, which avoids mess for the first and last items and just feels a bit cleaner.
1 parent 0ea9255 commit f0cc730

File tree

1 file changed

+25
-9
lines changed

1 file changed

+25
-9
lines changed

src/components/modules/table-content/hooks/use-active-toc.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,41 @@ import { useEffect } from 'react';
22

33
import { screens } from '@/styles';
44

5+
const getHeadings = () =>
6+
document.querySelectorAll<HTMLElement>('article :is(h2,h3,h4), div#intro');
7+
58
function useActiveToc() {
69
useEffect(() => {
710
const setCurrent: IntersectionObserverCallback = entries => {
11+
const scrollContainer = document
12+
.querySelector('#table-of-content-headings')
13+
?.parentElement;
14+
if (!scrollContainer) return;
15+
816
for (const entry of entries) {
917
const { id } = entry.target as HTMLElement;
1018
const isMobile = matchMedia(`(max-width: ${screens.lg})`);
11-
const tocHeadingEl = document.querySelector(`#table-of-content-headings a[href="#${id}"]`);
19+
20+
const tocHeadingEl = scrollContainer.querySelector<HTMLElement>(`a[href="#${id}"]`);
1221
if (!tocHeadingEl) return;
1322

1423
if (entry.isIntersecting) {
1524
// Remove active class from all toc items
16-
document.querySelectorAll('#table-of-content-headings a').forEach(e => e.classList.remove('active'));
25+
scrollContainer.querySelectorAll('a')
26+
.forEach(e => e.classList.remove('active'));
27+
28+
// Add active to the correct toc
1729
tocHeadingEl.classList.add('active');
30+
1831
if (!isMobile.matches) {
19-
tocHeadingEl.scrollIntoView({ block: 'nearest', inline: 'nearest' });
32+
const elementTop = tocHeadingEl.offsetTop;
33+
const containerHeight = scrollContainer.clientHeight;
34+
const scrollOffset = elementTop - (containerHeight / 2);
35+
36+
scrollContainer.scroll({
37+
top: scrollOffset,
38+
behavior: 'smooth'
39+
});
2040
}
2141
}
2242
}
@@ -29,14 +49,10 @@ function useActiveToc() {
2949

3050
const headingObserver = new IntersectionObserver(setCurrent, observerOptions);
3151

32-
document
33-
.querySelectorAll<HTMLElement>('article :is(h2,h3,h4), div#intro')
34-
.forEach(heading => headingObserver.observe(heading));
52+
getHeadings().forEach(heading => headingObserver.observe(heading));
3553

3654
return () => {
37-
document
38-
.querySelectorAll<HTMLElement>('article :is(h2,h3,h4), div#intro')
39-
.forEach(heading => headingObserver.unobserve(heading));
55+
getHeadings().forEach(heading => headingObserver.unobserve(heading));
4056
};
4157
}, []);
4258
}

0 commit comments

Comments
 (0)