React Scrollspy
Sun Nov 27 2022 14:29:05 GMT+0000 (Coordinated Universal Time)
Saved by
@avivdaniel
#javascript
import React, { useState, useEffect } from 'react';
import throttle from 'lodash/fp/throttle';
export interface useScrollSpyParams {
activeSectionDefault?: string; //The key of the section that should be active by default
offsetPx?: number;
sectionElementRefs: { [key: string]: React.RefObject<HTMLElement> }; //The key of each element should be uniq!
throttleMs?: number;
scrollingElement?: React.RefObject<HTMLElement>;
}
export const useScrollSpy = ({
activeSectionDefault = '',
offsetPx = 0,
scrollingElement,
sectionElementRefs = {},
throttleMs = 100,
}: useScrollSpyParams) => {
const [activeSection, setActiveSection] = useState(activeSectionDefault);
const scrollIntoView = (sectionKey: string) => {
const sectionElement = sectionElementRefs[sectionKey].current;
const scrollingContainer = scrollingElement?.current || window;
if (sectionElement && scrollingContainer) {
scrollingContainer.scrollTo({
top: sectionElement.getBoundingClientRect().top + offsetPx - 60,
behavior: 'smooth',
});
}
};
const handle = throttle(throttleMs, () => {
let currentSectionId = activeSection;
Object.entries(sectionElementRefs).forEach(([sectionKey, ref]) => {
const section = ref.current;
if (!section || !(section instanceof Element)) return;
// GetBoundingClientRect returns values relative to viewport
if (section.getBoundingClientRect().top + offsetPx < 0) {
currentSectionId = sectionKey;
return;
}
return;
});
setActiveSection(currentSectionId);
});
useEffect(() => {
const scrollable = scrollingElement?.current ?? window;
scrollable.addEventListener('scroll', handle);
// Run initially
handle();
return () => {
scrollable.removeEventListener('scroll', handle);
};
}, [sectionElementRefs, offsetPx, scrollingElement, handle]);
return { activeSection, scrollIntoView };
};
content_copyCOPY
Comments