From 1aff45d9354c1d09185811b7c270f39d22073b6d Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 8 Oct 2025 07:51:19 +0800 Subject: [PATCH] Perf fix for useScrollFn --- src/components/timeline.jsx | 29 +++--- src/locales/en.po | 24 ++--- src/utils/useScrollFn.js | 185 ++++++++++++++---------------------- 3 files changed, 93 insertions(+), 145 deletions(-) diff --git a/src/components/timeline.jsx b/src/components/timeline.jsx index 3084883f..9604e6c8 100644 --- a/src/components/timeline.jsx +++ b/src/components/timeline.jsx @@ -285,20 +285,8 @@ function Timeline({ const headerRef = useRef(); // const [hiddenUI, setHiddenUI] = useState(false); const [nearReachStart, setNearReachStart] = useState(false); - const { resetScrollDirection } = useScrollFn( - { - scrollableRef, - distanceFromEnd: 2, - scrollThresholdStart: 44, - }, - ({ - scrollDirection, - nearReachStart, - // nearReachEnd, - reachStart, - // reachEnd, - }) => { - // setHiddenUI(scrollDirection === 'end' && !nearReachEnd); + const scrollFnCallback = useCallback( + ({ scrollDirection, nearReachStart, reachStart }) => { if (headerRef.current) { const hiddenUI = scrollDirection === 'end' && !nearReachStart; headerRef.current.hidden = hiddenUI; @@ -307,11 +295,16 @@ function Timeline({ if (reachStart) { loadItems(true); } - // else if (nearReachEnd || (reachEnd && showMore)) { - // loadItems(); - // } }, - [], + [setNearReachStart, loadItems], + ); + const { resetScrollDirection } = useScrollFn( + { + scrollableRef, + distanceFromEnd: 2, + scrollThresholdStart: 44, + }, + scrollFnCallback, ); useEffect(() => { diff --git a/src/locales/en.po b/src/locales/en.po index 45a483c1..77e8f1e1 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -348,7 +348,7 @@ msgstr "More from <0/>" #: src/components/columns.jsx:27 #: src/components/nav-menu.jsx:181 #: src/components/shortcuts-settings.jsx:139 -#: src/components/timeline.jsx:471 +#: src/components/timeline.jsx:464 #: src/pages/catchup.jsx:899 #: src/pages/filters.jsx:90 #: src/pages/followed-hashtags.jsx:41 @@ -510,7 +510,7 @@ msgstr "Attachment #{i} failed" #: src/components/compose.jsx:1387 #: src/components/status.jsx:2350 -#: src/components/timeline.jsx:1016 +#: src/components/timeline.jsx:1009 msgid "Content warning" msgstr "" @@ -728,14 +728,14 @@ msgstr "Edit History Snapshots" #: src/components/edit-history-controls.jsx:35 #: src/components/gif-picker-modal.jsx:207 #: src/components/media-modal.jsx:469 -#: src/components/timeline.jsx:929 +#: src/components/timeline.jsx:922 msgid "Previous" msgstr "" #: src/components/edit-history-controls.jsx:47 #: src/components/gif-picker-modal.jsx:225 #: src/components/media-modal.jsx:488 -#: src/components/timeline.jsx:946 +#: src/components/timeline.jsx:939 msgid "Next" msgstr "" @@ -826,7 +826,7 @@ msgstr "" #: src/components/generic-accounts.jsx:214 #: src/components/quotes-modal.jsx:128 -#: src/components/timeline.jsx:553 +#: src/components/timeline.jsx:546 #: src/pages/list.jsx:321 #: src/pages/notifications.jsx:923 #: src/pages/search.jsx:562 @@ -836,7 +836,7 @@ msgstr "" #: src/components/generic-accounts.jsx:219 #: src/components/quotes-modal.jsx:132 -#: src/components/timeline.jsx:558 +#: src/components/timeline.jsx:551 #: src/pages/search.jsx:567 msgid "The end." msgstr "" @@ -1222,7 +1222,7 @@ msgstr "" #: src/components/status-compact.jsx:70 #: src/components/status.jsx:3204 #: src/components/status.jsx:3282 -#: src/components/timeline.jsx:1005 +#: src/components/timeline.jsx:998 #: src/pages/catchup.jsx:76 #: src/pages/catchup.jsx:1921 msgid "Filtered" @@ -2787,12 +2787,12 @@ msgstr "{index}/{total}" msgid "{index}/X" msgstr "{index}/X" -#: src/components/timeline.jsx:487 +#: src/components/timeline.jsx:480 #: src/pages/settings.jsx:1282 msgid "New posts" msgstr "" -#: src/components/timeline.jsx:588 +#: src/components/timeline.jsx:581 #: src/pages/home.jsx:228 #: src/pages/notifications.jsx:899 #: src/pages/status.jsx:1153 @@ -2801,16 +2801,16 @@ msgid "Try again" msgstr "" #. placeholder {0}: fItems.length -#: src/components/timeline.jsx:623 +#: src/components/timeline.jsx:616 msgid "{0, plural, one {# Boost} other {# Boosts}}" msgstr "{0, plural, one {# Boost} other {# Boosts}}" -#: src/components/timeline.jsx:628 +#: src/components/timeline.jsx:621 msgid "Pinned posts" msgstr "Pinned posts" #. placeholder {0}: filterInfo.titlesStr -#: src/components/timeline.jsx:1000 +#: src/components/timeline.jsx:993 msgid "<0>Filtered: <1>{0}" msgstr "" diff --git a/src/utils/useScrollFn.js b/src/utils/useScrollFn.js index 34e6ff12..03f5e283 100644 --- a/src/utils/useScrollFn.js +++ b/src/utils/useScrollFn.js @@ -14,121 +14,89 @@ export default function useScrollFn( init, } = {}, callback, - deps, ) { if (!callback) return; - // const [scrollDirection, setScrollDirection] = useState(null); - // const [reachStart, setReachStart] = useState(false); - // const [reachEnd, setReachEnd] = useState(false); - // const [nearReachStart, setNearReachStart] = useState(false); - // const [nearReachEnd, setNearReachEnd] = useState(false); const isVertical = direction === 'vertical'; const previousScrollStart = useRef(null); const scrollDirection = useRef(null); - const onScroll = useThrottledCallback(() => { - // let scrollDirection = null; - let reachStart = false; - let reachEnd = false; - let nearReachStart = false; - let nearReachEnd = false; + const onScroll = useThrottledCallback( + () => { + let reachStart = false; + let reachEnd = false; + let nearReachStart = false; + let nearReachEnd = false; - 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 + 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 scrollDelta = scrollStart - previousScrollStart.current; + const isScrollingForward = scrollDelta > 0; + const threshold = isScrollingForward ? scrollThresholdEnd - : scrollThresholdStart) - ) { - // setScrollDirection( - // previousScrollStart.current < scrollStart ? 'end' : 'start', - // ); - scrollDirection.current = - previousScrollStart.current < scrollStart ? 'end' : 'start'; - previousScrollStart.current = scrollStart; - } + : scrollThresholdStart; + const distanceFromStartPx = + _distanceFromStartPx || + Math.min( + clientDimension * distanceFromStart, + scrollDimension, + scrollStart, + ); + const distanceFromEndPx = + _distanceFromEndPx || + Math.min( + clientDimension * distanceFromEnd, + scrollDimension, + scrollDimension - scrollStart - clientDimension, + ); - // setReachStart(scrollStart <= 0); - // setReachEnd(scrollStart + clientDimension >= scrollDimension); - // setNearReachStart(scrollStart <= distanceFromStartPx); - // setNearReachEnd( - // scrollStart + clientDimension >= scrollDimension - distanceFromEndPx, - // ); - reachStart = scrollStart <= 0; - reachEnd = scrollStart + clientDimension >= scrollDimension; - nearReachStart = scrollStart <= distanceFromStartPx; - nearReachEnd = - scrollStart + clientDimension >= scrollDimension - distanceFromEndPx; + if (Math.abs(scrollDelta) >= threshold) { + scrollDirection.current = isScrollingForward ? 'end' : 'start'; + previousScrollStart.current = scrollStart; + } - callback({ - scrollDirection: scrollDirection.current, - reachStart, - reachEnd, - nearReachStart, - nearReachEnd, - }); - }, 500); + reachStart = scrollStart <= 0; + reachEnd = scrollStart + clientDimension >= scrollDimension; + nearReachStart = scrollStart <= distanceFromStartPx; + nearReachEnd = + scrollStart + clientDimension >= scrollDimension - distanceFromEndPx; + + callback({ + scrollDirection: scrollDirection.current, + reachStart, + reachEnd, + nearReachStart, + nearReachEnd, + }); + }, + 500, + { + leading: false, + }, + ); useLayoutEffect(() => { const scrollableElement = scrollableRef.current; - if (!scrollableElement) return {}; - previousScrollStart.current = - scrollableElement[isVertical ? 'scrollTop' : 'scrollLeft']; - - scrollableElement.addEventListener('scroll', onScroll, { passive: true }); - - return () => scrollableElement.removeEventListener('scroll', onScroll); - }, [ - distanceFromStart, - distanceFromEnd, - scrollThresholdStart, - scrollThresholdEnd, - ...deps, - ]); - - // useEffect(() => { - // callback({ - // scrollDirection, - // reachStart, - // reachEnd, - // nearReachStart, - // nearReachEnd, - // }); - // }, [ - // scrollDirection, - // reachStart, - // reachEnd, - // nearReachStart, - // nearReachEnd, - // ...deps, - // ]); + if (scrollableElement) { + previousScrollStart.current = + scrollableElement[isVertical ? 'scrollTop' : 'scrollLeft']; + scrollableElement.addEventListener('scroll', onScroll, { passive: true }); + } + return () => { + if (scrollableElement) { + scrollableElement.removeEventListener('scroll', onScroll); + } + }; + }, []); useEffect(() => { if (init && scrollableRef.current) { @@ -143,17 +111,4 @@ export default function useScrollFn( scrollDirection.current = null; }, }; - - // return { - // scrollDirection, - // reachStart, - // reachEnd, - // nearReachStart, - // nearReachEnd, - // init: () => { - // if (scrollableRef.current) { - // scrollableRef.current.dispatchEvent(new Event('scroll')); - // } - // }, - // }; }