react-scroll-snapper
Version:
Swipeable views for React using CSS scroll snap
64 lines (63 loc) • 2.66 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { useCallback, useEffect, useRef } from "react";
/**
* Scroll Snapper React component.
*/
export function ScrollSnapper(props) {
const { className = "", index, onIndexChange, onScroll, ...rootProps } = props;
const containerRef = useRef(null);
const scrollTimeout = useRef(null);
const lastChildrenCount = useRef(0);
// on every rerender
useEffect(() => {
if (!containerRef.current)
return;
// set aria-hidden and inert on all children that aren't current page
const currentChild = containerRef.current.children[index];
for (const child of containerRef.current.children) {
if (!(child instanceof HTMLElement))
continue;
const isCurrent = child === currentChild;
child.ariaHidden = !isCurrent ? "true" : null;
child.inert = !isCurrent;
}
// only if number of children changed
const childrenCount = containerRef.current.children.length;
if (childrenCount !== lastChildrenCount.current) {
lastChildrenCount.current = childrenCount;
// scroll container to the current page instantly
const pageWidth = containerRef.current.scrollWidth / containerRef.current.children.length;
containerRef.current.scrollTo({
behavior: "instant",
left: index * pageWidth,
top: 0,
});
}
});
// on page index change
useEffect(() => {
if (!containerRef.current)
return;
// scroll container to the current page smoothly
const pageWidth = containerRef.current.scrollWidth / containerRef.current.children.length;
containerRef.current.scrollTo({
behavior: "smooth",
left: index * pageWidth,
top: 0,
});
}, [index]);
// on user scroll
const handleScroll = useCallback((event) => {
onScroll?.(event);
const { currentTarget } = event;
if (scrollTimeout.current)
clearTimeout(scrollTimeout.current);
scrollTimeout.current = window.setTimeout(() => {
// update current page index
const pageWidth = currentTarget.scrollWidth / currentTarget.children.length;
const currentIndex = Math.round(currentTarget.scrollLeft / pageWidth);
onIndexChange(currentIndex, currentTarget);
}, 100);
}, [onIndexChange, onScroll]);
return (_jsx("div", { ...rootProps, ref: containerRef, className: `ScrollSnapper ${className}`, onScroll: handleScroll }));
}