fix: toc does not highlight clicked anchor + use scroll-margin-top (#5425)

* fix toc highlighting anchorTopOffset issues

* fix comment

* use ternary

* revert to previous offset
This commit is contained in:
Sébastien Lorber 2021-08-26 16:50:40 +02:00 committed by GitHub
parent 1f1c7f1695
commit 2a72c64581
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 8 deletions

View file

@ -49,7 +49,8 @@ const createAnchorHeading = (
aria-hidden="true"
tabIndex={-1}
className={clsx('anchor', `anchor__${Tag}`, {
[styles.enhancedAnchor]: !hideOnScroll,
[styles.anchorWithHideOnScrollNavbar]: hideOnScroll,
[styles.anchorWithStickyNavbar]: !hideOnScroll,
})}
id={id}
/>

View file

@ -5,6 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
.enhancedAnchor {
top: calc(var(--ifm-navbar-height) * -1 - 0.5rem);
/*
When the navbar is sticky, ensure that on anchor click,
the browser does not scroll that anchor behind the navbar
See https://twitter.com/JoshWComeau/status/1332015868725891076
*/
.anchorWithStickyNavbar {
scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
/* top: calc(var(--ifm-navbar-height) * -1 - 0.5rem); */
}
.anchorWithHideOnScrollNavbar {
scroll-margin-top: 0.5rem;
}

View file

@ -7,6 +7,7 @@
import {Params} from '@theme/hooks/useTOCHighlight';
import {useEffect, useRef} from 'react';
import {useThemeConfig} from '@docusaurus/theme-common';
// If the anchor has no height and is just a "marker" in the dom; we'll use the parent (normally the link text) rect boundaries instead
function getVisibleBoundingClientRect(element: HTMLElement): DOMRect {
@ -30,11 +31,13 @@ function getAnchors() {
return Array.from(document.querySelectorAll(selector)) as HTMLElement[];
}
function getActiveAnchor(): Element | null {
function getActiveAnchor({
anchorTopOffset,
}: {
anchorTopOffset: number;
}): Element | null {
const anchors = getAnchors();
const anchorTopOffset = 100; // Skip anchors that are too close to the viewport top
// Naming is hard
// The "nextVisibleAnchor" is the first anchor that appear under the viewport top boundary
// Note: it does not mean this anchor is visible yet, but if user continues scrolling down, it will be the first to become visible
@ -74,9 +77,30 @@ function getLinks(linkClassName: string) {
) as HTMLAnchorElement[];
}
function getNavbarHeight(): number {
// Not ideal to obtain actual height this way
// Using TS ! (not ?) because otherwise a bad selector would be un-noticed
return document.querySelector('.navbar')!.clientHeight;
}
function useAnchorTopOffsetRef() {
const anchorTopOffsetRef = useRef<number>(0);
const {
navbar: {hideOnScroll},
} = useThemeConfig();
useEffect(() => {
anchorTopOffsetRef.current = hideOnScroll ? 0 : getNavbarHeight();
}, [hideOnScroll]);
return anchorTopOffsetRef;
}
function useTOCHighlight(params: Params): void {
const lastActiveLinkRef = useRef<HTMLAnchorElement | undefined>(undefined);
const anchorTopOffsetRef = useAnchorTopOffsetRef();
useEffect(() => {
const {linkClassName, linkActiveClassName} = params;
@ -94,7 +118,9 @@ function useTOCHighlight(params: Params): void {
function updateActiveLink() {
const links = getLinks(linkClassName);
const activeAnchor = getActiveAnchor();
const activeAnchor = getActiveAnchor({
anchorTopOffset: anchorTopOffsetRef.current,
});
const activeLink = links.find(
(link) => activeAnchor && activeAnchor.id === getLinkAnchorValue(link),
);
@ -113,7 +139,7 @@ function useTOCHighlight(params: Params): void {
document.removeEventListener('scroll', updateActiveLink);
document.removeEventListener('resize', updateActiveLink);
};
}, [params]);
}, [params, anchorTopOffsetRef]);
}
export default useTOCHighlight;