feat(SW-441): Implemented useScrollSpy hook

This commit is contained in:
Erik Tiekstra
2024-10-01 13:01:21 +02:00
parent a567190080
commit 275d85f482
10 changed files with 138 additions and 28 deletions

70
hooks/useScrollSpy.ts Normal file
View File

@@ -0,0 +1,70 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
export default function useScrollSpy(
sectionIds: string[],
options: IntersectionObserverInit = {}
): {
activeSectionId: string
pauseScrollSpy: () => void
} {
const [activeSectionId, setActiveSectionId] = useState("")
const observerIsInactive = useRef(false)
const mergedOptions = useMemo(
() => ({
root: null,
// Make sure only to activate the section when it reaches the top of the viewport.
// A negative value for rootMargin shrinks the root bounding box inward,
// meaning elements will only be considered intersecting when they are further inside the viewport.
rootMargin: "-8% 0% -90% 0%",
threshold: 0,
...options,
}),
[options]
)
const handleIntersection = useCallback(
(entries: IntersectionObserverEntry[]) => {
if (observerIsInactive.current) {
return
}
const intersectingEntries: IntersectionObserverEntry[] = []
entries.forEach((e) => {
if (e.isIntersecting) {
intersectingEntries.push(e)
}
})
if (intersectingEntries.length) {
setActiveSectionId(intersectingEntries[0].target.id)
}
},
[]
)
useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, mergedOptions)
const elements = sectionIds
.map((id) => document.getElementById(id))
.filter(Boolean)
elements.forEach((element) => {
if (!element) {
return
}
observer.observe(element)
})
return () => elements.forEach((el) => el && observer.unobserve(el))
}, [sectionIds, mergedOptions, handleIntersection])
const pauseScrollSpy = () => {
observerIsInactive.current = true
setTimeout(() => {
observerIsInactive.current = false
}, 500)
}
return { activeSectionId, pauseScrollSpy }
}