Skip to content

Commit

Permalink
fix: toc does not highlight clicked anchor + use scroll-margin-top (#…
Browse files Browse the repository at this point in the history
…5425)

* fix toc highlighting anchorTopOffset issues

* fix comment

* use ternary

* revert to previous offset
  • Loading branch information
slorber authored Aug 26, 2021
1 parent 1f1c7f1 commit 2a72c64
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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),
);
Expand All @@ -113,7 +139,7 @@ function useTOCHighlight(params: Params): void {
document.removeEventListener('scroll', updateActiveLink);
document.removeEventListener('resize', updateActiveLink);
};
}, [params]);
}, [params, anchorTopOffsetRef]);
}

export default useTOCHighlight;

0 comments on commit 2a72c64

Please sign in to comment.