Skip to content

Instantly share code, notes, and snippets.

@vbfox
Created April 28, 2021 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vbfox/06823f8b5799b97dcb0434df4b736db4 to your computer and use it in GitHub Desktop.
Save vbfox/06823f8b5799b97dcb0434df4b736db4 to your computer and use it in GitHub Desktop.
Simulate a 'scrollstart' and 'scrollend' event by listening to scroll and debouncing the calls
/**
* Simulate a 'scrollstart' and 'scrollend' event by listening to scroll and debouncing the calls.
*
* The proposal to standardize part of this is https://github.com/w3c/csswg-drafts/issues/3744
*/
export function useScrollStartEndEvents(
ref: RefObject<HTMLElement>,
onScrollStart?: () => void,
onScrollEnd?: () => void,
debounceTimeMs: number = 100
) {
const isScrolling = useRef(false);
const scrollLeft = useRef(0);
const scrollTop = useRef(0);
const activeTimeout = useRef<number | undefined>();
const onTimeout = useCallback(() => {
if (ref.current === null) {
return;
}
const element = ref.current;
if (element.scrollLeft === scrollLeft.current && element.scrollTop === scrollTop.current) {
isScrolling.current = false;
activeTimeout.current = undefined;
onScrollEnd?.();
} else {
scrollLeft.current = element.scrollLeft;
scrollTop.current = element.scrollTop;
activeTimeout.current = (setTimeout(onTimeout, debounceTimeMs) as unknown) as number;
}
}, [ref, debounceTimeMs, onScrollEnd]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const onScroll = useCallback(
throttleRaf(() => {
if (ref.current === null) {
return;
}
if (!isScrolling.current) {
const element = ref.current;
isScrolling.current = true;
scrollLeft.current = element.scrollLeft;
scrollTop.current = element.scrollTop;
onScrollStart?.();
}
if (activeTimeout.current !== undefined) {
clearTimeout(activeTimeout.current);
}
activeTimeout.current = (setTimeout(onTimeout, debounceTimeMs) as unknown) as number;
}),
[ref, onScrollStart, debounceTimeMs, onTimeout]
);
useEffect(() => {
if (ref.current === null) {
return;
}
const element = ref.current;
element.addEventListener('scroll', onScroll);
return () => element.removeEventListener('scroll', onScroll);
}, [ref, onScroll]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment