Infinite Scroll: Prevent scroll jumps in global search and member list

This commit is contained in:
Alexander Zinchuk 2021-05-12 22:22:21 +03:00
parent 6ad6494be1
commit 81f7ab1639
4 changed files with 11 additions and 82 deletions

View File

@ -187,6 +187,8 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
className="LeftSearch custom-scroll"
items={foundMessages}
onLoadMore={handleLoadMore}
// To prevent scroll jumps caused by delayed local results rendering
noScrollRestoreOnTop
noFastList
>
{dateSearchQuery && (

View File

@ -314,6 +314,8 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
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}

View File

@ -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<OwnProps> = ({
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<OwnProps> = ({
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<HTMLDivElement>) => {
if (loadMoreForwards && loadMoreBackwards) {

View File

@ -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<HTMLDivElement>;
className?: string;
onLoadMore: ({ direction }: { direction: LoadMoreDirection }) => void;
onScroll?: (e: UIEvent<HTMLDivElement>) => void;
items: any[];
sensitiveArea?: number;
preloadBackwards?: number;
children: any;
};
const DEFAULT_SENSITIVE_AREA = 1200;
const DEFAULT_PRELOAD_BACKWARDS = 20;
const SimpleInfiniteScroll: FC<OwnProps> = ({
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<HTMLDivElement>(null);
if (ref) {
containerRef = ref;
}
// eslint-disable-next-line no-null/no-null
const anchorTopRef = useRef<number>(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<HTMLDivElement>) => {
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 (
<div ref={containerRef} className={className} onScroll={handleScroll}>
{children}
</div>
);
};
export default SimpleInfiniteScroll;