mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-10 12:57:46 +00:00
Merge pull request #515 from unicorn-utterances/fix-smooth-scrolling-toc
Prevent table of contents smooth scrolling when clicked link is out of view
This commit is contained in:
@@ -34,15 +34,54 @@ const { headingsToDisplaySlugs } = Astro.props as {
|
|||||||
id: li.firstElementChild.getAttribute("href").slice(1),
|
id: li.firstElementChild.getAttribute("href").slice(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// return whether an element is currently visible within a scrolling container
|
||||||
|
function isVisibleInContainer(
|
||||||
|
container: HTMLElement,
|
||||||
|
child: HTMLElement
|
||||||
|
): boolean {
|
||||||
|
// child element is above the lowest point of the container...
|
||||||
|
return (
|
||||||
|
container.scrollTop + container.offsetHeight >
|
||||||
|
child.offsetTop + child.offsetHeight &&
|
||||||
|
// child element is below the highest point of the container...
|
||||||
|
container.scrollTop < child.offsetTop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// smooth-scroll to a heading when clicked
|
// smooth-scroll to a heading when clicked
|
||||||
function handleAnchorClick(e: Event) {
|
function handleAnchorClick(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const anchor = e.target as HTMLAnchorElement;
|
const anchor = e.target as HTMLAnchorElement;
|
||||||
document
|
const li = anchor.parentElement as HTMLLIElement;
|
||||||
.getElementById(anchor.getAttribute("href").slice(1))
|
const heading = document.getElementById(
|
||||||
?.scrollIntoView({
|
anchor.getAttribute("href").slice(1)
|
||||||
behavior: prefersReducedMotion ? "auto" : "smooth",
|
);
|
||||||
|
const isLiVisible = isVisibleInContainer(tocListContainer, li);
|
||||||
|
|
||||||
|
/*
|
||||||
|
This needs to check that both the active and clicked ToC entries
|
||||||
|
are visible within the scroll container before performing a smooth
|
||||||
|
scroll to the clicked heading.
|
||||||
|
|
||||||
|
Otherwise, since the IntersectionObserver receives continual updates
|
||||||
|
during a smooth scroll (which there is no way to detect), the ToC will
|
||||||
|
interrupt the page scroll, since (most) browsers cannot smooth scroll
|
||||||
|
two containers at once.
|
||||||
|
*/
|
||||||
|
const activeLi = tocListContainer.querySelector(
|
||||||
|
".toc-is-active"
|
||||||
|
) as HTMLLIElement;
|
||||||
|
const isActiveVisible =
|
||||||
|
!activeLi || isVisibleInContainer(tocListContainer, activeLi);
|
||||||
|
|
||||||
|
heading?.scrollIntoView({
|
||||||
|
// only use smooth scrolling if the heading is currently within the TOC scroll area
|
||||||
|
behavior:
|
||||||
|
prefersReducedMotion || !(activeLi && isActiveVisible && isLiVisible)
|
||||||
|
? "auto"
|
||||||
|
: "smooth",
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +109,8 @@ const { headingsToDisplaySlugs } = Astro.props as {
|
|||||||
// the user hasn't requested reduced motion...
|
// the user hasn't requested reduced motion...
|
||||||
!prefersReducedMotion &&
|
!prefersReducedMotion &&
|
||||||
tocListContainer &&
|
tocListContainer &&
|
||||||
// the link is below the lowest point of the container...
|
// the link is not currently visible in the container..
|
||||||
(tocListContainer.scrollTop + tocListContainer.offsetHeight <
|
!isVisibleInContainer(tocListContainer, linkRef.li)
|
||||||
linkRef.li.offsetTop + linkRef.li.offsetHeight ||
|
|
||||||
// the link is above the highest point of the container...
|
|
||||||
tocListContainer.scrollTop > linkRef.li.offsetTop)
|
|
||||||
) {
|
) {
|
||||||
// ...then scroll to center the link in the container
|
// ...then scroll to center the link in the container
|
||||||
tocListContainer.scrollTo({
|
tocListContainer.scrollTo({
|
||||||
|
|||||||
Reference in New Issue
Block a user