Message List: Fix scroll animation
This commit is contained in:
parent
880a6c6977
commit
a244d64670
@ -1,8 +1,6 @@
|
|||||||
.MessageList {
|
.MessageList {
|
||||||
--action-message-bg: var(--pattern-color);
|
--action-message-bg: var(--pattern-color);
|
||||||
|
|
||||||
scroll-snap-type: y proximity;
|
|
||||||
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -38,8 +36,8 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-bottom-snap {
|
&.with-bottom-snap {
|
||||||
scroll-snap-type: none;
|
scroll-snap-type: y proximity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container {
|
.messages-container {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC } from '@teact';
|
|
||||||
import { beginHeavyAnimation, memo, useEffect, useMemo, useRef } from '@teact';
|
import { beginHeavyAnimation, memo, useEffect, useMemo, useRef } from '@teact';
|
||||||
import { addExtraClass, removeExtraClass } from '@teact/teact-dom';
|
import { addExtraClass, removeExtraClass } from '@teact/teact-dom';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
@ -170,7 +169,7 @@ const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000;
|
|||||||
const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000;
|
const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
const BOTTOM_THRESHOLD = 50;
|
const BOTTOM_THRESHOLD = 50;
|
||||||
const BOTTOM_SNAP_THRESHOLD = 10;
|
const BOTTOM_SNAP_THRESHOLD = 7;
|
||||||
|
|
||||||
const UNREAD_DIVIDER_TOP = 10;
|
const UNREAD_DIVIDER_TOP = 10;
|
||||||
const SCROLL_DEBOUNCE = 200;
|
const SCROLL_DEBOUNCE = 200;
|
||||||
@ -180,11 +179,11 @@ const SELECT_MODE_ANIMATION_DURATION = 200;
|
|||||||
|
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
const FORCE_MESSAGES_SCROLL_CLASS = 'force-messages-scroll';
|
const FORCE_MESSAGES_SCROLL_CLASS = 'force-messages-scroll';
|
||||||
const NO_BOTTOM_SNAP_CLASS = 'no-bottom-snap';
|
const BOTTOM_SNAP_CLASS = 'with-bottom-snap';
|
||||||
|
|
||||||
const runDebouncedForScroll = debounce((cb) => cb(), SCROLL_DEBOUNCE, false);
|
const runDebouncedForScroll = debounce((cb) => cb(), SCROLL_DEBOUNCE, false);
|
||||||
|
|
||||||
const MessageList: FC<OwnProps & StateProps> = ({
|
const MessageList = ({
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
type,
|
type,
|
||||||
@ -243,7 +242,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
}) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||||
loadMessageViews, loadPeerStoriesByIds, loadFactChecks, requestChatTranslation,
|
loadMessageViews, loadPeerStoriesByIds, loadFactChecks, requestChatTranslation,
|
||||||
@ -471,6 +470,33 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const { isScrolled, updateStickyDates } = useStickyDates();
|
const { isScrolled, updateStickyDates } = useStickyDates();
|
||||||
|
|
||||||
|
const updateBottomSnapClass = useLastCallback(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
const bottomTrigger = container?.querySelector<HTMLDivElement>('.fab-trigger');
|
||||||
|
if (!container || !bottomTrigger) return;
|
||||||
|
|
||||||
|
// Check if fab-trigger + threshold are entering the viewport
|
||||||
|
const viewportBottom = container.scrollTop + container.offsetHeight;
|
||||||
|
const triggerPosition = bottomTrigger.offsetTop;
|
||||||
|
// Scroll is near fab-trigger + threshold. Prevents snap on sponsored message
|
||||||
|
const shouldSnapBeActive = triggerPosition - BOTTOM_SNAP_THRESHOLD <= viewportBottom
|
||||||
|
&& viewportBottom <= triggerPosition + BOTTOM_SNAP_THRESHOLD * 2;
|
||||||
|
|
||||||
|
const hasSnap = container.classList.contains(BOTTOM_SNAP_CLASS);
|
||||||
|
if (hasSnap === shouldSnapBeActive) return;
|
||||||
|
|
||||||
|
if (shouldSnapBeActive) {
|
||||||
|
requestMutation(() => {
|
||||||
|
addExtraClass(container, BOTTOM_SNAP_CLASS);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearTimeout(scrollSnapDisabledTimerRef.current);
|
||||||
|
requestMutation(() => {
|
||||||
|
removeExtraClass(container, BOTTOM_SNAP_CLASS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleScroll = useLastCallback(() => {
|
const handleScroll = useLastCallback(() => {
|
||||||
if (isScrollTopJustUpdatedRef.current) {
|
if (isScrollTopJustUpdatedRef.current) {
|
||||||
isScrollTopJustUpdatedRef.current = false;
|
isScrollTopJustUpdatedRef.current = false;
|
||||||
@ -486,6 +512,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
updateStickyDates(container);
|
updateStickyDates(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateBottomSnapClass();
|
||||||
|
|
||||||
runDebouncedForScroll(() => {
|
runDebouncedForScroll(() => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
|
||||||
@ -509,28 +537,14 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
||||||
|
|
||||||
const handleWheel = useLastCallback((e: React.WheelEvent<HTMLDivElement>) => {
|
const handleWheel = useLastCallback((e: React.WheelEvent<HTMLDivElement>) => {
|
||||||
// Firefox is finicky about bottom scroll snapping, so we enable it only when nearing the bottom
|
// Remove snap when scrolling up to avoid scroll bug
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1753188
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1753188
|
||||||
if (!IS_FIREFOX) return;
|
if (IS_FIREFOX && e.deltaY < 0) {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const scrollTop = container.scrollTop;
|
|
||||||
const scrollHeight = container.scrollHeight;
|
|
||||||
const offsetHeight = container.offsetHeight;
|
|
||||||
const isNearBottomForSnap = scrollTop >= scrollHeight - offsetHeight - BOTTOM_SNAP_THRESHOLD;
|
|
||||||
if (!isNearBottomForSnap) return;
|
|
||||||
|
|
||||||
if (e.deltaY < 0) {
|
|
||||||
clearTimeout(scrollSnapDisabledTimerRef.current);
|
|
||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
addExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
removeExtraClass(container, BOTTOM_SNAP_CLASS);
|
||||||
container.scrollBy(0, -BOTTOM_SNAP_THRESHOLD); // Manually scroll to prevent ignoring first event
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
requestMutation(() => {
|
|
||||||
removeExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -645,10 +659,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
if (wasMessageAdded) {
|
if (wasMessageAdded) {
|
||||||
clearTimeout(scrollSnapDisabledTimerRef.current);
|
clearTimeout(scrollSnapDisabledTimerRef.current);
|
||||||
|
|
||||||
addExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
removeExtraClass(container, BOTTOM_SNAP_CLASS);
|
||||||
|
|
||||||
scrollSnapDisabledTimerRef.current = window.setTimeout(() => {
|
scrollSnapDisabledTimerRef.current = window.setTimeout(() => {
|
||||||
removeExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
requestMutation(() => {
|
||||||
|
addExtraClass(container, BOTTOM_SNAP_CLASS);
|
||||||
|
});
|
||||||
}, MESSAGE_ANIMATION_DURATION);
|
}, MESSAGE_ANIMATION_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,7 +784,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
!isReady && 'is-animating',
|
!isReady && 'is-animating',
|
||||||
hasOpenChatButton && 'saved-dialog',
|
hasOpenChatButton && 'saved-dialog',
|
||||||
isChatProtected && 'hide-on-print',
|
isChatProtected && 'hide-on-print',
|
||||||
IS_FIREFOX && NO_BOTTOM_SNAP_CLASS,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasMessages = Boolean((messageIds && messageGroups) || lastMessage);
|
const hasMessages = Boolean((messageIds && messageGroups) || lastMessage);
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { memo, useRef } from '@teact';
|
import { memo, useRef } from '@teact';
|
||||||
|
|
||||||
import { FocusDirection } from '../../types';
|
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import useFocusMessageListElement from './message/hooks/useFocusMessageListElement';
|
import useFocusMessageListElement from './message/hooks/useFocusMessageListElement';
|
||||||
@ -19,7 +17,6 @@ const MessageListBottomMarker = ({ isFocused, className }: OwnProps) => {
|
|||||||
isJustAdded: true,
|
isJustAdded: true,
|
||||||
isFocused,
|
isFocused,
|
||||||
noFocusHighlight: true,
|
noFocusHighlight: true,
|
||||||
focusDirection: FocusDirection.Down,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { setExtraStyles } from '@teact/teact-dom';
|
||||||
import { beginHeavyAnimation } from '../lib/teact/teact';
|
import { beginHeavyAnimation } from '../lib/teact/teact';
|
||||||
import { getGlobal } from '../global';
|
import { getGlobal } from '../global';
|
||||||
|
|
||||||
@ -140,6 +141,10 @@ function createMutateFunction(args: AnimateScrollArgs) {
|
|||||||
|
|
||||||
isAnimating = true;
|
isAnimating = true;
|
||||||
|
|
||||||
|
setExtraStyles(container, {
|
||||||
|
scrollSnapType: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
const prevOnHeavyAnimationEnd = onHeavyAnimationEnd;
|
const prevOnHeavyAnimationEnd = onHeavyAnimationEnd;
|
||||||
onHeavyAnimationEnd = beginHeavyAnimation(undefined, true);
|
onHeavyAnimationEnd = beginHeavyAnimation(undefined, true);
|
||||||
prevOnHeavyAnimationEnd?.();
|
prevOnHeavyAnimationEnd?.();
|
||||||
@ -155,6 +160,9 @@ function createMutateFunction(args: AnimateScrollArgs) {
|
|||||||
|
|
||||||
if (!isAnimating) {
|
if (!isAnimating) {
|
||||||
currentArgs = undefined;
|
currentArgs = undefined;
|
||||||
|
setExtraStyles(container, {
|
||||||
|
scrollSnapType: '',
|
||||||
|
});
|
||||||
|
|
||||||
onHeavyAnimationEnd?.();
|
onHeavyAnimationEnd?.();
|
||||||
onHeavyAnimationEnd = undefined;
|
onHeavyAnimationEnd = undefined;
|
||||||
@ -170,7 +178,13 @@ export function isAnimatingScroll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function cancelScrollBlockingAnimation() {
|
export function cancelScrollBlockingAnimation() {
|
||||||
onHeavyAnimationEnd!();
|
if (currentArgs?.container) {
|
||||||
|
setExtraStyles(currentArgs.container, {
|
||||||
|
scrollSnapType: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onHeavyAnimationEnd?.();
|
||||||
onHeavyAnimationEnd = undefined;
|
onHeavyAnimationEnd = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user