From 81f7ab16394df7a2ad18c5067202902a640e1c8d Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 12 May 2021 22:22:21 +0300 Subject: [PATCH] Infinite Scroll: Prevent scroll jumps in global search and member list --- src/components/left/search/ChatResults.tsx | 2 + src/components/right/Profile.tsx | 2 + src/components/ui/InfiniteScroll.tsx | 8 ++- src/components/ui/SimpleInfiniteScroll.tsx | 81 ---------------------- 4 files changed, 11 insertions(+), 82 deletions(-) delete mode 100644 src/components/ui/SimpleInfiniteScroll.tsx diff --git a/src/components/left/search/ChatResults.tsx b/src/components/left/search/ChatResults.tsx index 312915bc1..3f39dda15 100644 --- a/src/components/left/search/ChatResults.tsx +++ b/src/components/left/search/ChatResults.tsx @@ -187,6 +187,8 @@ const ChatResults: FC = ({ className="LeftSearch custom-scroll" items={foundMessages} onLoadMore={handleLoadMore} + // To prevent scroll jumps caused by delayed local results rendering + noScrollRestoreOnTop noFastList > {dateSearchQuery && ( diff --git a/src/components/right/Profile.tsx b/src/components/right/Profile.tsx index 40f5ca571..f87eac467 100644 --- a/src/components/right/Profile.tsx +++ b/src/components/right/Profile.tsx @@ -314,6 +314,8 @@ const Profile: FC = ({ cacheBuster={cacheBuster} sensitiveArea={PROFILE_SENSITIVE_AREA} preloadBackwards={canRenderContents ? (resultType === 'members' ? MEMBERS_SLICE : SHARED_MEDIA_SLICE) : 0} + // To prevent scroll jumps caused by reordering member list + noScrollRestoreOnTop noFastList onLoadMore={getMore} onScroll={handleScroll} diff --git a/src/components/ui/InfiniteScroll.tsx b/src/components/ui/InfiniteScroll.tsx index 2cd357a25..43400abf2 100644 --- a/src/components/ui/InfiniteScroll.tsx +++ b/src/components/ui/InfiniteScroll.tsx @@ -18,6 +18,7 @@ type OwnProps = { preloadBackwards?: number; sensitiveArea?: number; noScrollRestore?: boolean; + noScrollRestoreOnTop?: boolean; noFastList?: boolean; cacheBuster?: any; children: any; @@ -38,6 +39,7 @@ const InfiniteScroll: FC = ({ sensitiveArea = DEFAULT_SENSITIVE_AREA, // Used to turn off restoring scroll position (e.g. for frequently re-ordered chat or user lists) noScrollRestore = false, + noScrollRestoreOnTop = false, noFastList, // Used to re-query `listItemElements` if rendering is delayed by transition cacheBuster, @@ -110,10 +112,14 @@ const InfiniteScroll: FC = ({ return; } + if (noScrollRestoreOnTop && container.scrollTop === 0) { + return; + } + resetScroll(container, newScrollTop); state.isScrollTopJustUpdated = true; - }, [noScrollRestore, itemSelector, items, cacheBuster]); + }, [items, itemSelector, noScrollRestore, noScrollRestoreOnTop, cacheBuster]); const handleScroll = useCallback((e: UIEvent) => { if (loadMoreForwards && loadMoreBackwards) { diff --git a/src/components/ui/SimpleInfiniteScroll.tsx b/src/components/ui/SimpleInfiniteScroll.tsx deleted file mode 100644 index 458bded74..000000000 --- a/src/components/ui/SimpleInfiniteScroll.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { RefObject, UIEvent } from 'react'; -import { LoadMoreDirection } from '../../types'; - -import React, { - FC, useCallback, useEffect, useMemo, useRef, -} from '../../lib/teact/teact'; - -import { debounce } from '../../util/schedulers'; - -type OwnProps = { - ref?: RefObject; - className?: string; - onLoadMore: ({ direction }: { direction: LoadMoreDirection }) => void; - onScroll?: (e: UIEvent) => void; - items: any[]; - sensitiveArea?: number; - preloadBackwards?: number; - children: any; -}; - -const DEFAULT_SENSITIVE_AREA = 1200; -const DEFAULT_PRELOAD_BACKWARDS = 20; - -const SimpleInfiniteScroll: FC = ({ - ref, - className, - onLoadMore, - onScroll, - items, - sensitiveArea = DEFAULT_SENSITIVE_AREA, - preloadBackwards = DEFAULT_PRELOAD_BACKWARDS, - children, -}: OwnProps) => { - // eslint-disable-next-line no-null/no-null - let containerRef = useRef(null); - if (ref) { - containerRef = ref; - } - - // eslint-disable-next-line no-null/no-null - const anchorTopRef = useRef(null); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const onLoadMoreDebounced = useMemo(() => debounce(onLoadMore, 1000, true, false), [onLoadMore, items]); - - useEffect(() => { - if (!items || items.length < preloadBackwards) { - onLoadMoreDebounced({ direction: LoadMoreDirection.Backwards }); - } - }, [items, onLoadMoreDebounced, preloadBackwards]); - - const handleScroll = useCallback((e: UIEvent) => { - if (onScroll) { - onScroll(e); - } - const container = e.target as HTMLElement; - const anchor = container.firstElementChild; - if (!anchor) { - return; - } - - const { scrollTop, scrollHeight, offsetHeight } = container; - const newAnchorTop = anchor.getBoundingClientRect().top; - const isNearBottom = scrollHeight - (scrollTop + offsetHeight) <= sensitiveArea; - const isMovingDown = typeof anchorTopRef.current === 'number' && newAnchorTop < anchorTopRef.current; - - if (isNearBottom && isMovingDown) { - onLoadMoreDebounced({ direction: LoadMoreDirection.Backwards }); - } - - anchorTopRef.current = newAnchorTop; - }, [onLoadMoreDebounced, onScroll, sensitiveArea]); - - return ( -
- {children} -
- ); -}; - -export default SimpleInfiniteScroll;