Message List: Prevent scroll jumps when selecting (again)

This commit is contained in:
Alexander Zinchuk 2021-08-21 13:33:26 +03:00
parent 2d7b28401f
commit 9170dc1863

View File

@ -333,23 +333,30 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
}, FOCUSING_DURATION); }, FOCUSING_DURATION);
} }
const { scrollTop, scrollHeight, offsetHeight } = container;
const scrollOffset = scrollOffsetRef.current!;
const lastItemElement = listItemElementsRef.current[listItemElementsRef.current.length - 1];
const isAtBottom = isViewportNewest && prevIsViewportNewest && (
scrollOffset - (prevContainerHeight || offsetHeight) <= BOTTOM_THRESHOLD
);
let newScrollTop!: number;
const hasFirstMessageChanged = messageIds && prevMessageIds && messageIds[0] !== prevMessageIds[0]; const hasFirstMessageChanged = messageIds && prevMessageIds && messageIds[0] !== prevMessageIds[0];
const hasLastMessageChanged = ( const hasLastMessageChanged = (
messageIds && prevMessageIds && messageIds[messageIds.length - 1] !== prevMessageIds[prevMessageIds.length - 1] messageIds && prevMessageIds && messageIds[messageIds.length - 1] !== prevMessageIds[prevMessageIds.length - 1]
); );
const wasMessageAdded = hasLastMessageChanged && !hasFirstMessageChanged;
const isAlreadyFocusing = messageIds && memoFocusingIdRef.current === messageIds[messageIds.length - 1]; const isAlreadyFocusing = messageIds && memoFocusingIdRef.current === messageIds[messageIds.length - 1];
if (isAtBottom && hasLastMessageChanged && !hasFirstMessageChanged && !isAlreadyFocusing) { const { scrollTop, scrollHeight, offsetHeight } = container;
const scrollOffset = scrollOffsetRef.current!;
const lastItemElement = listItemElementsRef.current[listItemElementsRef.current.length - 1];
let bottomOffset = scrollOffset - (prevContainerHeight || offsetHeight);
if (wasMessageAdded) {
// If two new messages come at once (e.g. when bot responds) then the first message will update `scrollOffset`
// right away (before animation) which creates inconsistency until the animation completes. To workaround that,
// we calculate `isAtBottom` with a "buffer" of the latest message height (this is approximate).
const lastItemHeight = lastItemElement ? lastItemElement.offsetHeight : 0;
bottomOffset -= lastItemHeight;
}
const isAtBottom = isViewportNewest && prevIsViewportNewest && bottomOffset <= BOTTOM_THRESHOLD;
let newScrollTop!: number;
if (wasMessageAdded && isAtBottom && !isAlreadyFocusing) {
if (lastItemElement) { if (lastItemElement) {
fastRaf(() => { fastRaf(() => {
fastSmoothScroll( fastSmoothScroll(
@ -431,10 +438,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf); const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf);
const noAvatars = Boolean(!withUsers || isChannelChat); const noAvatars = Boolean(!withUsers || isChannelChat);
const shouldRenderGreeting = isChatPrivate(chatId) && !isChatWithSelf && !isBot const shouldRenderGreeting = isChatPrivate(chatId) && !isChatWithSelf && !isBot
&& (( && (
!messageGroups && !lastMessage && messageIds (
// Used to avoid flickering when deleting a greeting that has just been sent !messageGroups && !lastMessage && messageIds
&& (!listItemElementsRef.current || listItemElementsRef.current.length === 0)) // Used to avoid flickering when deleting a greeting that has just been sent
&& (!listItemElementsRef.current || listItemElementsRef.current.length === 0)
)
|| checkSingleMessageActionByType('contactSignUp', messageGroups) || checkSingleMessageActionByType('contactSignUp', messageGroups)
|| (lastMessage && lastMessage.content.action && lastMessage.content.action.type === 'contactSignUp') || (lastMessage && lastMessage.content.action && lastMessage.content.action.type === 'contactSignUp')
); );
@ -508,12 +517,12 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
function checkSingleMessageActionByType(type: ApiAction['type'], messageGroups?: MessageDateGroup[]) { function checkSingleMessageActionByType(type: ApiAction['type'], messageGroups?: MessageDateGroup[]) {
return messageGroups return messageGroups
&& messageGroups.length === 1 && messageGroups.length === 1
&& messageGroups[0].senderGroups.length === 1 && messageGroups[0].senderGroups.length === 1
&& messageGroups[0].senderGroups[0].length === 1 && messageGroups[0].senderGroups[0].length === 1
&& 'content' in messageGroups[0].senderGroups[0][0] && 'content' in messageGroups[0].senderGroups[0][0]
&& messageGroups[0].senderGroups[0][0].content.action && messageGroups[0].senderGroups[0][0].content.action
&& messageGroups[0].senderGroups[0][0].content.action.type === type; && messageGroups[0].senderGroups[0][0].content.action.type === type;
} }
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(