Fix jumping chat while scroll animation is active
Fix scroll to top position
This commit is contained in:
parent
fec0fdf72f
commit
51d35d71e3
@ -142,7 +142,7 @@ export class AnimationIntersector {
|
|||||||
animation.autoplay &&
|
animation.autoplay &&
|
||||||
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group)
|
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group)
|
||||||
) {
|
) {
|
||||||
console.warn('play animation:', animation);
|
//console.warn('play animation:', animation);
|
||||||
animation.play();
|
animation.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,9 @@ import AudioElement from "../audio";
|
|||||||
import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
|
import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
|
||||||
import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
|
import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
|
||||||
import { FocusDirection } from "../../helpers/fastSmoothScroll";
|
import { FocusDirection } from "../../helpers/fastSmoothScroll";
|
||||||
import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
|
import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent } from "../../hooks/useHeavyAnimationCheck";
|
||||||
import { fastRaf } from "../../helpers/schedulers";
|
import { fastRaf } from "../../helpers/schedulers";
|
||||||
|
import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise";
|
||||||
|
|
||||||
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
|
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
|
||||||
|
|
||||||
@ -295,30 +296,35 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {
|
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {
|
||||||
const {peerId, mids} = e.detail;
|
const {peerId, mids} = e.detail;
|
||||||
|
|
||||||
(mids as number[]).forEach(mid => {
|
|
||||||
/* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve();
|
|
||||||
promise.then(() => {
|
|
||||||
|
|
||||||
}); */
|
const middleware = this.getMiddleware();
|
||||||
this.needUpdate.forEachReverse((obj, idx) => {
|
getHeavyAnimationPromise().then(() => {
|
||||||
if(obj.replyMid === mid, obj.replyToPeerId === peerId) {
|
if(!middleware()) return;
|
||||||
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
|
|
||||||
|
(mids as number[]).forEach(mid => {
|
||||||
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
|
/* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve();
|
||||||
const bubble = this.bubbles[mid];
|
promise.then(() => {
|
||||||
if(!bubble) return;
|
|
||||||
|
}); */
|
||||||
const message = this.chat.getMessage(mid);
|
this.needUpdate.forEachReverse((obj, idx) => {
|
||||||
|
if(obj.replyMid === mid, obj.replyToPeerId === peerId) {
|
||||||
const repliedMessage = this.appMessagesManager.getMessageByPeer(obj.replyToPeerId, replyMid);
|
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
|
||||||
if(repliedMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение
|
|
||||||
delete message.reply_to_mid; // ! WARNING!
|
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
|
||||||
|
const bubble = this.bubbles[mid];
|
||||||
|
if(!bubble) return;
|
||||||
|
|
||||||
|
const message = this.chat.getMessage(mid);
|
||||||
|
|
||||||
|
const repliedMessage = this.appMessagesManager.getMessageByPeer(obj.replyToPeerId, replyMid);
|
||||||
|
if(repliedMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение
|
||||||
|
delete message.reply_to_mid; // ! WARNING!
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderMessage(message, true, false, bubble, false);
|
||||||
|
//this.renderMessage(message, true, true, bubble, false);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
this.renderMessage(message, true, false, bubble, false);
|
|
||||||
//this.renderMessage(message, true, true, bubble, false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1421,7 +1427,7 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setMessagesQueuePromise() {
|
public setMessagesQueuePromise() {
|
||||||
if(this.messagesQueuePromise) return;
|
if(this.messagesQueuePromise || !this.messagesQueue.length) return;
|
||||||
|
|
||||||
this.messagesQueuePromise = new Promise((resolve, reject) => {
|
this.messagesQueuePromise = new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -1525,7 +1531,7 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
const peerId = this.peerId;
|
const peerId = this.peerId;
|
||||||
// * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right)
|
// * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right)
|
||||||
const our = message.fromId == rootScope.myId || (message.pFlags.out && this.appPeersManager.isMegagroup(this.peerId));
|
const our = message.fromId === rootScope.myId || (message.pFlags.out && this.appPeersManager.isMegagroup(this.peerId));
|
||||||
|
|
||||||
const messageDiv = document.createElement('div');
|
const messageDiv = document.createElement('div');
|
||||||
messageDiv.classList.add('message');
|
messageDiv.classList.add('message');
|
||||||
@ -1553,7 +1559,7 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const save = ['is-highlighted'];
|
const save = ['is-highlighted', 'zoom-fade'];
|
||||||
const wasClassNames = bubble.className.split(' ');
|
const wasClassNames = bubble.className.split(' ');
|
||||||
const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c)));
|
const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c)));
|
||||||
bubble.className = classNames.join(' ');
|
bubble.className = classNames.join(' ');
|
||||||
@ -1561,9 +1567,11 @@ export default class ChatBubbles {
|
|||||||
bubbleContainer = bubble.lastElementChild as HTMLDivElement;
|
bubbleContainer = bubble.lastElementChild as HTMLDivElement;
|
||||||
bubbleContainer.innerHTML = '';
|
bubbleContainer.innerHTML = '';
|
||||||
//bubbleContainer.style.marginBottom = '';
|
//bubbleContainer.style.marginBottom = '';
|
||||||
|
const animationDelay = bubbleContainer.style.animationDelay;
|
||||||
bubbleContainer.style.cssText = '';
|
bubbleContainer.style.cssText = '';
|
||||||
|
bubbleContainer.style.animationDelay = animationDelay;
|
||||||
|
|
||||||
if(bubble == this.firstUnreadBubble) {
|
if(bubble === this.firstUnreadBubble) {
|
||||||
bubble.classList.add('is-first-unread');
|
bubble.classList.add('is-first-unread');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2514,7 +2522,9 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
////console.timeEnd('render history total');
|
////console.timeEnd('render history total');
|
||||||
|
|
||||||
return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId);
|
return getHeavyAnimationPromise().then(() => {
|
||||||
|
return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId);
|
||||||
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.log.error('getHistory error:', err);
|
this.log.error('getHistory error:', err);
|
||||||
return false;
|
return false;
|
||||||
@ -2533,7 +2543,9 @@ export default class ChatBubbles {
|
|||||||
cached = true;
|
cached = true;
|
||||||
this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad);
|
this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad);
|
||||||
processResult(result);
|
processResult(result);
|
||||||
promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId);
|
promise = getHeavyAnimationPromise().then(() => {
|
||||||
|
return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId);
|
||||||
|
});
|
||||||
//return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise);
|
//return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise);
|
||||||
//return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
|
//return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
|
||||||
}
|
}
|
||||||
@ -2544,21 +2556,33 @@ export default class ChatBubbles {
|
|||||||
waitPromise.then(() => {
|
waitPromise.then(() => {
|
||||||
if(rootScope.settings.animationsEnabled) {
|
if(rootScope.settings.animationsEnabled) {
|
||||||
const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid));
|
const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid));
|
||||||
|
const animationPromise = deferredPromise<void>();
|
||||||
|
|
||||||
|
let lastMsDelay = 0;
|
||||||
mids.forEach((mid, idx) => {
|
mids.forEach((mid, idx) => {
|
||||||
const bubble = this.bubbles[mid];
|
const bubble = this.bubbles[mid];
|
||||||
|
|
||||||
|
lastMsDelay = ((idx || 0.1) * 10);
|
||||||
//if(idx || isSafari) {
|
//if(idx || isSafari) {
|
||||||
// ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL !
|
// ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL !
|
||||||
bubble.style.animationDelay = ((idx || 0.1) * 10) + 'ms';
|
bubble.style.animationDelay = lastMsDelay + 'ms';
|
||||||
//}
|
//}
|
||||||
|
|
||||||
bubble.classList.add('zoom-fade');
|
bubble.classList.add('zoom-fade');
|
||||||
bubble.addEventListener('animationend', () => {
|
bubble.addEventListener('animationend', () => {
|
||||||
bubble.style.animationDelay = '';
|
bubble.style.animationDelay = '';
|
||||||
bubble.classList.remove('zoom-fade');
|
bubble.classList.remove('zoom-fade');
|
||||||
|
|
||||||
|
if(idx === (mids.length - 1)) {
|
||||||
|
animationPromise.resolve();
|
||||||
|
}
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
//this.log('supa', bubble);
|
//this.log('supa', bubble);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(mids.length) {
|
||||||
|
dispatchHeavyAnimationEvent(animationPromise, lastMsDelay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -19,6 +19,7 @@ export function cancelAnimationByKey(key: AnimationInstanceKey) {
|
|||||||
const instance = getAnimationInstance(key);
|
const instance = getAnimationInstance(key);
|
||||||
if(instance) {
|
if(instance) {
|
||||||
instance.isCancelled = true;
|
instance.isCancelled = true;
|
||||||
|
instance.deferred.resolve();
|
||||||
instances.delete(key);
|
instances.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,7 +32,9 @@ export function animateSingle(tick: Function, key: AnimationInstanceKey, instanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
fastRaf(() => {
|
fastRaf(() => {
|
||||||
if(instance.isCancelled) return;
|
if(instance.isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(tick()) {
|
if(tick()) {
|
||||||
animateSingle(tick, key, instance);
|
animateSingle(tick, key, instance);
|
||||||
|
@ -95,7 +95,7 @@ function scrollWithJs(
|
|||||||
|
|
||||||
switch(position) {
|
switch(position) {
|
||||||
case 'start':
|
case 'start':
|
||||||
path = (elementPosition - margin) - scrollPosition;
|
path = elementPosition - margin;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
//path = (elementTop + elementHeight + margin) - containerHeight;
|
//path = (elementTop + elementHeight + margin) - containerHeight;
|
||||||
|
@ -4,31 +4,43 @@
|
|||||||
import { AnyToVoidFunction } from '../types';
|
import { AnyToVoidFunction } from '../types';
|
||||||
import ListenerSetter from '../helpers/listenerSetter';
|
import ListenerSetter from '../helpers/listenerSetter';
|
||||||
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
|
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
|
||||||
|
import { pause } from '../helpers/schedulers';
|
||||||
|
|
||||||
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
||||||
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
||||||
|
|
||||||
let isAnimating = false;
|
let isAnimating = false;
|
||||||
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
||||||
let lastAnimationPromise: Promise<any>;
|
let promisesInQueue = 0;
|
||||||
|
|
||||||
export const dispatchHeavyAnimationEvent = (promise: Promise<any>) => {
|
export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: number) => {
|
||||||
if(!isAnimating) {
|
if(!isAnimating) {
|
||||||
heavyAnimationPromise = deferredPromise<void>();
|
heavyAnimationPromise = deferredPromise<void>();
|
||||||
|
document.dispatchEvent(new Event(ANIMATION_START_EVENT));
|
||||||
|
isAnimating = true;
|
||||||
|
console.log('dispatchHeavyAnimationEvent: start');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
++promisesInQueue;
|
||||||
|
console.log('dispatchHeavyAnimationEvent: attach promise, length:', promisesInQueue);
|
||||||
|
|
||||||
document.dispatchEvent(new Event(ANIMATION_START_EVENT));
|
const promises = [
|
||||||
isAnimating = true;
|
timeout !== undefined ? pause(timeout) : undefined,
|
||||||
lastAnimationPromise = promise;
|
promise.finally(() => {})
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
promise.then(() => {
|
const perf = performance.now();
|
||||||
if(lastAnimationPromise !== promise) {
|
Promise.race(promises).then(() => {
|
||||||
return;
|
--promisesInQueue;
|
||||||
|
console.log('dispatchHeavyAnimationEvent: promise end, length:', promisesInQueue, performance.now() - perf);
|
||||||
|
if(!promisesInQueue) {
|
||||||
|
isAnimating = false;
|
||||||
|
promisesInQueue = 0;
|
||||||
|
document.dispatchEvent(new Event(ANIMATION_END_EVENT));
|
||||||
|
heavyAnimationPromise.resolve();
|
||||||
|
|
||||||
|
console.log('dispatchHeavyAnimationEvent: end');
|
||||||
}
|
}
|
||||||
|
|
||||||
isAnimating = false;
|
|
||||||
document.dispatchEvent(new Event(ANIMATION_END_EVENT));
|
|
||||||
heavyAnimationPromise.resolve();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return heavyAnimationPromise;
|
return heavyAnimationPromise;
|
||||||
|
@ -126,7 +126,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&.is-highlighted, &.is-selected {
|
&.is-highlighted, &.is-selected {
|
||||||
&:after {
|
&:after {
|
||||||
top: #{2rem + $bubble-margin} !important;
|
top: calc(#{$bubble-margin / 2} + 30px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,7 +370,7 @@ $bubble-margin: .25rem;
|
|||||||
&.is-group-last {
|
&.is-group-last {
|
||||||
margin-bottom: #{$bubble-margin * 2};
|
margin-bottom: #{$bubble-margin * 2};
|
||||||
|
|
||||||
&:before, &:after {
|
&:after {
|
||||||
bottom: -#{$bubble-margin};
|
bottom: -#{$bubble-margin};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +386,7 @@ $bubble-margin: .25rem;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-group-first {
|
&.is-group-first {
|
||||||
&:before, &:after {
|
&:after {
|
||||||
top: -#{$bubble-margin};
|
top: -#{$bubble-margin};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user