feat(SW-441): Implemented useScrollSpy hook
This commit is contained in:
70
hooks/useScrollSpy.ts
Normal file
70
hooks/useScrollSpy.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user