Upgrade UI design and improve some functions
This commit is contained in:
48
hooks/useActiveHeading.tsx
Normal file
48
hooks/useActiveHeading.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as React from "react";
|
||||
|
||||
function removeFirstHash(str: string): string {
|
||||
return str.replace(/^#/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook to highlight a heading a user is currently reading.
|
||||
* @param headingList List of links to the headings (ex. "#title")
|
||||
* @param options Options for the Intersectionobserver (ex. rootMargin)
|
||||
* @returns The Id/Link to the heading, that is currently active.
|
||||
*/
|
||||
export function useActiveHeading(headingList: string[], options?: IntersectionObserverInit) {
|
||||
const [activeId, setActiveId] = React.useState("");
|
||||
|
||||
React.useEffect(() => {
|
||||
const callback: IntersectionObserverCallback = (headingEntries) => {
|
||||
// Get all headings that are currently visible on the page
|
||||
const visibleHeadings = headingEntries.filter((e) => e.isIntersecting);
|
||||
|
||||
if (visibleHeadings.length === 0) {
|
||||
//Necessary if a user scrolls down and then reloads.
|
||||
//In that case the IntersectionObserver didn't see the Heading onscreen
|
||||
const element = headingEntries.reverse().find((e) => e.boundingClientRect.bottom < 150);
|
||||
if (element) {
|
||||
setActiveId(`#${element.target.id}`);
|
||||
}
|
||||
} else {
|
||||
// If there is more than one visible heading,
|
||||
// choose the one that is closest to the top of the page
|
||||
// the entries are always sorted top to bottom.
|
||||
setActiveId(`#${visibleHeadings[0].target.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(callback, options);
|
||||
|
||||
//Observe all (non-null) elements
|
||||
headingList
|
||||
.map((heading) => document?.querySelector(`[id='${removeFirstHash(heading)}']`))
|
||||
//Remove null elments
|
||||
.flatMap((f) => (!!f ? [f] : []))
|
||||
.forEach((element) => observer.observe(element));
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [headingList, options]);
|
||||
return activeId;
|
||||
}
|
||||
Reference in New Issue
Block a user