From d8ceb03d74a699749a08cf9d3cbc84917c27cfab Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 2 Jan 2024 12:25:25 +0800 Subject: [PATCH] Throttle scroll events --- src/utils/useScrollFn.js | 104 ++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/src/utils/useScrollFn.js b/src/utils/useScrollFn.js index ffa677ba..20141e2c 100644 --- a/src/utils/useScrollFn.js +++ b/src/utils/useScrollFn.js @@ -1,4 +1,5 @@ -import { useEffect, useLayoutEffect, useState } from 'preact/hooks'; +import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks'; +import { useThrottledCallback } from 'use-debounce'; export default function useScrollFn( { @@ -22,59 +23,62 @@ export default function useScrollFn( const [nearReachStart, setNearReachStart] = useState(false); const [nearReachEnd, setNearReachEnd] = useState(false); const isVertical = direction === 'vertical'; + const previousScrollStart = useRef(null); + + const onScroll = useThrottledCallback(() => { + const scrollableElement = scrollableRef.current; + const { + scrollTop, + scrollLeft, + scrollHeight, + scrollWidth, + clientHeight, + clientWidth, + } = scrollableElement; + const scrollStart = isVertical ? scrollTop : scrollLeft; + const scrollDimension = isVertical ? scrollHeight : scrollWidth; + const clientDimension = isVertical ? clientHeight : clientWidth; + const scrollDistance = Math.abs(scrollStart - previousScrollStart.current); + const distanceFromStartPx = + _distanceFromStartPx || + Math.min( + clientDimension * distanceFromStart, + scrollDimension, + scrollStart, + ); + const distanceFromEndPx = + _distanceFromEndPx || + Math.min( + clientDimension * distanceFromEnd, + scrollDimension, + scrollDimension - scrollStart - clientDimension, + ); + + if ( + scrollDistance >= + (previousScrollStart.current < scrollStart + ? scrollThresholdEnd + : scrollThresholdStart) + ) { + setScrollDirection( + previousScrollStart.current < scrollStart ? 'end' : 'start', + ); + previousScrollStart.current = scrollStart; + } + + setReachStart(scrollStart <= 0); + setReachEnd(scrollStart + clientDimension >= scrollDimension); + setNearReachStart(scrollStart <= distanceFromStartPx); + setNearReachEnd( + scrollStart + clientDimension >= scrollDimension - distanceFromEndPx, + ); + }, 500); useLayoutEffect(() => { const scrollableElement = scrollableRef.current; if (!scrollableElement) return {}; - let previousScrollStart = isVertical - ? scrollableElement.scrollTop - : scrollableElement.scrollLeft; - - function onScroll() { - const { - scrollTop, - scrollLeft, - scrollHeight, - scrollWidth, - clientHeight, - clientWidth, - } = scrollableElement; - const scrollStart = isVertical ? scrollTop : scrollLeft; - const scrollDimension = isVertical ? scrollHeight : scrollWidth; - const clientDimension = isVertical ? clientHeight : clientWidth; - const scrollDistance = Math.abs(scrollStart - previousScrollStart); - const distanceFromStartPx = - _distanceFromStartPx || - Math.min( - clientDimension * distanceFromStart, - scrollDimension, - scrollStart, - ); - const distanceFromEndPx = - _distanceFromEndPx || - Math.min( - clientDimension * distanceFromEnd, - scrollDimension, - scrollDimension - scrollStart - clientDimension, - ); - - if ( - scrollDistance >= - (previousScrollStart < scrollStart - ? scrollThresholdEnd - : scrollThresholdStart) - ) { - setScrollDirection(previousScrollStart < scrollStart ? 'end' : 'start'); - previousScrollStart = scrollStart; - } - - setReachStart(scrollStart <= 0); - setReachEnd(scrollStart + clientDimension >= scrollDimension); - setNearReachStart(scrollStart <= distanceFromStartPx); - setNearReachEnd( - scrollStart + clientDimension >= scrollDimension - distanceFromEndPx, - ); - } + previousScrollStart.current = + scrollableElement[isVertical ? 'scrollTop' : 'scrollLeft']; scrollableElement.addEventListener('scroll', onScroll, { passive: true });