Skip to content

Instantly share code, notes, and snippets.

@mlynch
Last active May 11, 2020 01:10
Show Gist options
  • Save mlynch/d49ffe3df347dbed50a239f5158dd1f3 to your computer and use it in GitHub Desktop.
Save mlynch/d49ffe3df347dbed50a239f5158dd1f3 to your computer and use it in GitHub Desktop.
Scroll Spy Provider
const Heading = (props: Props) => {
const ref = useRef<HTMLHeadingElement>();
const ScrollSpy = useContext(ScrollSpyContext);
if (
ref.current &&
["H1", "H2", "H3"].indexOf(ref.current.tagName) >= 0
) {
value?.observer!.observe(ref.current);
}
// ....
};
import { useEffect, useCallback, createContext, useState, useRef } from "react";
interface Props {
children: React.ReactNode;
}
type ScrollSpyValue = {
observer: IntersectionObserver | null;
visible: Element[];
};
const ScrollSpyContext = createContext<ScrollSpyValue | null>(null);
const ScrollSpyProvider = ({ children }: Props) => {
const [observer, setObserver] = useState<IntersectionObserver | null>(null);
const [visible, setVisible] = useState<Element[]>([]);
let value = {observer, visible};
const handleIntersection = useCallback(
(entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
entries.forEach(e => {
if (e.intersectionRatio > 0) {
setVisible(prevVisible => [...prevVisible, e.target]);
} else {
setVisible(prevVisible => prevVisible.filter(v => v !== e.target));
}
})
},
[visible, setVisible]
);
useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, {
threshold: 0,
});
setObserver(observer);
return () => {
observer.disconnect();
};
}, []);
return (
<ScrollSpyContext.Provider value={value}>
{children}
</ScrollSpyContext.Provider>
);
};
const ScrollSpyConsumer = ScrollSpyContext.Consumer;
export { ScrollSpyContext, ScrollSpyProvider, ScrollSpyConsumer };
const TableOfContents = ({ doc }: TableOfContentsProps) => {
const headings = getHeadings(doc);
const ScrollSpy = useContext(ScrollSpyContext);
return (
<div>
// Check ScrollSpy.visible against your child elements
// and activate one if it matches.
// For example, I'm looping through each of my known headings
// and activating the first one that matches in value.visible
// and keeping the last activated one in case none match
}}
</div>
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment