Message List: Fix scroll jumps
This commit is contained in:
parent
cdf291933d
commit
ab9cef0f13
@ -5,7 +5,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
|
||||||
overflow-anchor: none;
|
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@ -42,7 +42,7 @@ import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
|||||||
import useOnChange from '../../hooks/useOnChange';
|
import useOnChange from '../../hooks/useOnChange';
|
||||||
import useStickyDates from './hooks/useStickyDates';
|
import useStickyDates from './hooks/useStickyDates';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import resetScroll from '../../util/resetScroll';
|
import resetScroll, { patchChromiumScroll } from '../../util/resetScroll';
|
||||||
import fastSmoothScroll, { isAnimatingScroll } from '../../util/fastSmoothScroll';
|
import fastSmoothScroll, { isAnimatingScroll } from '../../util/fastSmoothScroll';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -210,12 +210,17 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const { isScrolled, updateStickyDates } = useStickyDates();
|
const { isScrolled, updateStickyDates } = useStickyDates();
|
||||||
|
|
||||||
|
const isScrollingRef = useRef<boolean>();
|
||||||
|
const isScrollPatchNeededRef = useRef<boolean>();
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
const handleScroll = useCallback(() => {
|
||||||
if (isScrollTopJustUpdatedRef.current) {
|
if (isScrollTopJustUpdatedRef.current) {
|
||||||
isScrollTopJustUpdatedRef.current = false;
|
isScrollTopJustUpdatedRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isScrollingRef.current = true;
|
||||||
|
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
|
|
||||||
if (!memoFocusingIdRef.current) {
|
if (!memoFocusingIdRef.current) {
|
||||||
@ -223,6 +228,8 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
runDebouncedForScroll(() => {
|
runDebouncedForScroll(() => {
|
||||||
|
isScrollingRef.current = false;
|
||||||
|
|
||||||
fastRaf(() => {
|
fastRaf(() => {
|
||||||
if (!container.parentElement) {
|
if (!container.parentElement) {
|
||||||
return;
|
return;
|
||||||
@ -320,7 +327,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
listItemElementsRef.current = Array.from(container.querySelectorAll<HTMLDivElement>('.message-list-item'));
|
listItemElementsRef.current = Array.from(container.querySelectorAll<HTMLDivElement>('.message-list-item'));
|
||||||
|
|
||||||
// During animation
|
// TODO Consider removing
|
||||||
if (!container.offsetParent) {
|
if (!container.offsetParent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -355,6 +362,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, offsetHeight } = container;
|
const { scrollTop, scrollHeight, offsetHeight } = container;
|
||||||
|
// TODO Consider `scrollOffset = scrollHeight - scrollTop`
|
||||||
const scrollOffset = scrollOffsetRef.current!;
|
const scrollOffset = scrollOffsetRef.current!;
|
||||||
const lastItemElement = listItemElementsRef.current[listItemElementsRef.current.length - 1];
|
const lastItemElement = listItemElementsRef.current[listItemElementsRef.current.length - 1];
|
||||||
|
|
||||||
@ -397,6 +405,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight;
|
const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight;
|
||||||
|
// TODO Look up within active transition slide
|
||||||
const anchor = anchorIdRef.current && document.getElementById(anchorIdRef.current);
|
const anchor = anchorIdRef.current && document.getElementById(anchorIdRef.current);
|
||||||
const unreadDivider = (
|
const unreadDivider = (
|
||||||
!anchor
|
!anchor
|
||||||
@ -411,6 +420,11 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
newScrollTop = scrollHeight - offsetHeight;
|
newScrollTop = scrollHeight - offsetHeight;
|
||||||
} else if (anchor) {
|
} else if (anchor) {
|
||||||
|
if (isScrollPatchNeededRef.current) {
|
||||||
|
isScrollPatchNeededRef.current = false;
|
||||||
|
patchChromiumScroll(container);
|
||||||
|
}
|
||||||
|
|
||||||
const newAnchorTop = anchor.getBoundingClientRect().top;
|
const newAnchorTop = anchor.getBoundingClientRect().top;
|
||||||
newScrollTop = scrollTop + (newAnchorTop - (anchorTopRef.current || 0));
|
newScrollTop = scrollTop + (newAnchorTop - (anchorTopRef.current || 0));
|
||||||
} else if (unreadDivider) {
|
} else if (unreadDivider) {
|
||||||
@ -515,6 +529,8 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
type={type}
|
type={type}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
|
isScrollingRef={isScrollingRef}
|
||||||
|
isScrollPatchNeededRef={isScrollPatchNeededRef}
|
||||||
threadTopMessageId={threadTopMessageId}
|
threadTopMessageId={threadTopMessageId}
|
||||||
hasLinkedChat={hasLinkedChat}
|
hasLinkedChat={hasLinkedChat}
|
||||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||||
|
|||||||
@ -31,6 +31,8 @@ interface OwnProps {
|
|||||||
threadId: number;
|
threadId: number;
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
|
isScrollingRef: { current: boolean | undefined };
|
||||||
|
isScrollPatchNeededRef: { current: boolean | undefined };
|
||||||
threadTopMessageId: number | undefined;
|
threadTopMessageId: number | undefined;
|
||||||
hasLinkedChat: boolean | undefined;
|
hasLinkedChat: boolean | undefined;
|
||||||
isSchedule: boolean;
|
isSchedule: boolean;
|
||||||
@ -56,6 +58,8 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
threadId,
|
threadId,
|
||||||
type,
|
type,
|
||||||
isReady,
|
isReady,
|
||||||
|
isScrollingRef,
|
||||||
|
isScrollPatchNeededRef,
|
||||||
threadTopMessageId,
|
threadTopMessageId,
|
||||||
hasLinkedChat,
|
hasLinkedChat,
|
||||||
isSchedule,
|
isSchedule,
|
||||||
@ -83,6 +87,8 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
onFabToggle,
|
onFabToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
isReady,
|
isReady,
|
||||||
|
isScrollingRef,
|
||||||
|
isScrollPatchNeededRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|||||||
@ -5,10 +5,11 @@ import { useMemo, useRef } from '../../../lib/teact/teact';
|
|||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
import { MessageListType } from '../../../global/types';
|
import { MessageListType } from '../../../global/types';
|
||||||
|
|
||||||
|
import { LOCAL_MESSAGE_ID_BASE, MESSAGE_LIST_SLICE } from '../../../config';
|
||||||
|
import { IS_MAC_OS, IS_SCROLL_PATCH_NEEDED, MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/environment';
|
||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
|
||||||
import { LOCAL_MESSAGE_ID_BASE, MESSAGE_LIST_SENSITIVE_AREA } from '../../../config';
|
|
||||||
import resetScroll from '../../../util/resetScroll';
|
import resetScroll from '../../../util/resetScroll';
|
||||||
|
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useOnChange from '../../../hooks/useOnChange';
|
import useOnChange from '../../../hooks/useOnChange';
|
||||||
|
|
||||||
const FAB_THRESHOLD = 50;
|
const FAB_THRESHOLD = 50;
|
||||||
@ -24,6 +25,8 @@ export default function useScrollHooks(
|
|||||||
onFabToggle: AnyToVoidFunction,
|
onFabToggle: AnyToVoidFunction,
|
||||||
onNotchToggle: AnyToVoidFunction,
|
onNotchToggle: AnyToVoidFunction,
|
||||||
isReady: boolean,
|
isReady: boolean,
|
||||||
|
isScrollingRef: { current: boolean | undefined },
|
||||||
|
isScrollPatchNeededRef: { current: boolean | undefined },
|
||||||
) {
|
) {
|
||||||
const { loadViewportMessages } = getDispatch();
|
const { loadViewportMessages } = getDispatch();
|
||||||
|
|
||||||
@ -91,9 +94,17 @@ export default function useScrollHooks(
|
|||||||
const { target } = triggerEntry;
|
const { target } = triggerEntry;
|
||||||
|
|
||||||
if (target.className === 'backwards-trigger') {
|
if (target.className === 'backwards-trigger') {
|
||||||
|
if (
|
||||||
|
IS_SCROLL_PATCH_NEEDED && isScrollingRef.current && messageIds.length <= MESSAGE_LIST_SLICE
|
||||||
|
) {
|
||||||
|
isScrollPatchNeededRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Consider removing
|
||||||
resetScroll(containerRef.current!);
|
resetScroll(containerRef.current!);
|
||||||
loadMoreBackwards();
|
loadMoreBackwards();
|
||||||
} else if (target.className === 'forwards-trigger') {
|
} else if (target.className === 'forwards-trigger') {
|
||||||
|
// TODO Consider removing
|
||||||
resetScroll(containerRef.current!);
|
resetScroll(containerRef.current!);
|
||||||
loadMoreForwards();
|
loadMoreForwards();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,6 @@ const isBigScreen = typeof window !== 'undefined' && window.innerHeight >= 900;
|
|||||||
|
|
||||||
export const MIN_PASSWORD_LENGTH = 1;
|
export const MIN_PASSWORD_LENGTH = 1;
|
||||||
|
|
||||||
export const MESSAGE_LIST_SENSITIVE_AREA = 750;
|
|
||||||
export const MESSAGE_LIST_SLICE = isBigScreen ? 60 : 40;
|
export const MESSAGE_LIST_SLICE = isBigScreen ? 60 : 40;
|
||||||
export const MESSAGE_LIST_VIEWPORT_LIMIT = MESSAGE_LIST_SLICE * 2;
|
export const MESSAGE_LIST_VIEWPORT_LIMIT = MESSAGE_LIST_SLICE * 2;
|
||||||
|
|
||||||
|
|||||||
@ -39,9 +39,11 @@ export const IS_MAC_OS = PLATFORM_ENV === 'macOS';
|
|||||||
export const IS_IOS = PLATFORM_ENV === 'iOS';
|
export const IS_IOS = PLATFORM_ENV === 'iOS';
|
||||||
export const IS_ANDROID = PLATFORM_ENV === 'Android';
|
export const IS_ANDROID = PLATFORM_ENV === 'Android';
|
||||||
export const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
export const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
export const IS_PWA = window.matchMedia('(display-mode: standalone)').matches
|
export const IS_PWA = (
|
||||||
|| (window.navigator as any).standalone
|
window.matchMedia('(display-mode: standalone)').matches
|
||||||
|| document.referrer.includes('android-app://');
|
|| (window.navigator as any).standalone
|
||||||
|
|| document.referrer.includes('android-app://')
|
||||||
|
);
|
||||||
|
|
||||||
export const IS_TOUCH_ENV = window.matchMedia('(pointer: coarse)').matches;
|
export const IS_TOUCH_ENV = window.matchMedia('(pointer: coarse)').matches;
|
||||||
// Keep in mind the landscape orientation
|
// Keep in mind the landscape orientation
|
||||||
@ -82,3 +84,8 @@ if (IS_MOV_SUPPORTED) SUPPORTED_VIDEO_CONTENT_TYPES.add(VIDEO_MOV_TYPE);
|
|||||||
export const DPR = window.devicePixelRatio || 1;
|
export const DPR = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
export const MASK_IMAGE_DISABLED = true;
|
export const MASK_IMAGE_DISABLED = true;
|
||||||
|
|
||||||
|
export const IS_SCROLL_PATCH_NEEDED = !IS_MAC_OS && !IS_IOS && !IS_ANDROID;
|
||||||
|
|
||||||
|
// Smaller area reduces scroll jumps caused by `patchChromiumScroll`
|
||||||
|
export const MESSAGE_LIST_SENSITIVE_AREA = IS_SCROLL_PATCH_NEEDED ? 300 : 750;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { IS_IOS } from './environment';
|
import { IS_IOS } from './environment';
|
||||||
|
import forceReflow from './forceReflow';
|
||||||
|
|
||||||
export default (container: HTMLDivElement, scrollTop?: number) => {
|
export default (container: HTMLDivElement, scrollTop?: number) => {
|
||||||
if (IS_IOS) {
|
if (IS_IOS) {
|
||||||
@ -13,3 +14,10 @@ export default (container: HTMLDivElement, scrollTop?: number) => {
|
|||||||
container.style.overflow = '';
|
container.style.overflow = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=1264266
|
||||||
|
export function patchChromiumScroll(element: HTMLElement) {
|
||||||
|
element.style.display = 'none';
|
||||||
|
forceReflow(element);
|
||||||
|
element.style.display = '';
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user