2022-12-21 19:03:32 +00:00
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
useContext,
|
|
|
|
useEffect,
|
|
|
|
useLayoutEffect,
|
|
|
|
useState,
|
|
|
|
} from 'react'
|
|
|
|
import { createStore, useStore } from 'zustand'
|
|
|
|
|
|
|
|
import { remToPx } from '@/lib/remToPx'
|
|
|
|
|
|
|
|
function createSectionStore(sections) {
|
|
|
|
return createStore((set) => ({
|
|
|
|
sections,
|
|
|
|
visibleSections: [],
|
|
|
|
setVisibleSections: (visibleSections) =>
|
|
|
|
set((state) =>
|
|
|
|
state.visibleSections.join() === visibleSections.join()
|
|
|
|
? {}
|
|
|
|
: { visibleSections }
|
|
|
|
),
|
|
|
|
registerHeading: ({ id, ref, offsetRem }) =>
|
2023-01-29 06:56:36 +00:00
|
|
|
set((state) => ({
|
|
|
|
sections: state.sections.map((section) => {
|
|
|
|
if (section.id === id) {
|
|
|
|
return {
|
|
|
|
...section,
|
|
|
|
headingRef: ref,
|
|
|
|
offsetRem,
|
2022-12-21 19:03:32 +00:00
|
|
|
}
|
2023-01-29 06:56:36 +00:00
|
|
|
}
|
|
|
|
return section
|
|
|
|
}),
|
|
|
|
})),
|
2022-12-21 19:03:32 +00:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
function useVisibleSections(sectionStore) {
|
2023-01-29 06:56:36 +00:00
|
|
|
const setVisibleSections = useStore(sectionStore, (s) => s.setVisibleSections)
|
|
|
|
const sections = useStore(sectionStore, (s) => s.sections)
|
2022-12-21 19:03:32 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
function checkVisibleSections() {
|
2023-01-29 06:56:36 +00:00
|
|
|
const { innerHeight, scrollY } = window
|
|
|
|
const newVisibleSections = []
|
2022-12-21 19:03:32 +00:00
|
|
|
|
|
|
|
for (
|
|
|
|
let sectionIndex = 0;
|
|
|
|
sectionIndex < sections.length;
|
|
|
|
sectionIndex++
|
|
|
|
) {
|
2023-01-29 06:56:36 +00:00
|
|
|
const { id, headingRef, offsetRem } = sections[sectionIndex]
|
|
|
|
const offset = remToPx(offsetRem)
|
|
|
|
const top = headingRef.current.getBoundingClientRect().top + scrollY
|
2022-12-21 19:03:32 +00:00
|
|
|
|
|
|
|
if (sectionIndex === 0 && top - offset > scrollY) {
|
|
|
|
newVisibleSections.push('_top')
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:56:36 +00:00
|
|
|
const nextSection = sections[sectionIndex + 1]
|
|
|
|
const bottom =
|
2022-12-21 19:03:32 +00:00
|
|
|
(nextSection?.headingRef.current.getBoundingClientRect().top ??
|
|
|
|
Infinity) +
|
|
|
|
scrollY -
|
|
|
|
remToPx(nextSection?.offsetRem ?? 0)
|
|
|
|
|
|
|
|
if (
|
|
|
|
(top > scrollY && top < scrollY + innerHeight) ||
|
|
|
|
(bottom > scrollY && bottom < scrollY + innerHeight) ||
|
|
|
|
(top <= scrollY && bottom >= scrollY + innerHeight)
|
|
|
|
) {
|
|
|
|
newVisibleSections.push(id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setVisibleSections(newVisibleSections)
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:56:36 +00:00
|
|
|
const raf = window.requestAnimationFrame(() => checkVisibleSections())
|
2022-12-21 19:03:32 +00:00
|
|
|
window.addEventListener('scroll', checkVisibleSections, { passive: true })
|
|
|
|
window.addEventListener('resize', checkVisibleSections)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
window.cancelAnimationFrame(raf)
|
|
|
|
window.removeEventListener('scroll', checkVisibleSections)
|
|
|
|
window.removeEventListener('resize', checkVisibleSections)
|
|
|
|
}
|
|
|
|
}, [setVisibleSections, sections])
|
|
|
|
}
|
|
|
|
|
|
|
|
const SectionStoreContext = createContext()
|
|
|
|
|
|
|
|
const useIsomorphicLayoutEffect =
|
|
|
|
typeof window === 'undefined' ? useEffect : useLayoutEffect
|
|
|
|
|
|
|
|
export function SectionProvider({ sections, children }) {
|
2023-01-29 06:56:36 +00:00
|
|
|
const [sectionStore] = useState(() => createSectionStore(sections))
|
2022-12-21 19:03:32 +00:00
|
|
|
|
|
|
|
useVisibleSections(sectionStore)
|
|
|
|
|
|
|
|
useIsomorphicLayoutEffect(() => {
|
|
|
|
sectionStore.setState({ sections })
|
|
|
|
}, [sectionStore, sections])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<SectionStoreContext.Provider value={sectionStore}>
|
|
|
|
{children}
|
|
|
|
</SectionStoreContext.Provider>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useSectionStore(selector) {
|
2023-01-29 06:56:36 +00:00
|
|
|
const store = useContext(SectionStoreContext)
|
2022-12-21 19:03:32 +00:00
|
|
|
return useStore(store, selector)
|
|
|
|
}
|