Browse Source

Fix jumping chat while scroll animation is active

Fix scroll to top position
master
morethanwords 4 years ago
parent
commit
51d35d71e3
  1. 2
      src/components/animationIntersector.ts
  2. 86
      src/components/chat/bubbles.ts
  3. 5
      src/helpers/animation.ts
  4. 2
      src/helpers/fastSmoothScroll.ts
  5. 36
      src/hooks/useHeavyAnimationCheck.ts
  6. 6
      src/scss/partials/_chatBubble.scss

2
src/components/animationIntersector.ts

@ -142,7 +142,7 @@ export class AnimationIntersector { @@ -142,7 +142,7 @@ export class AnimationIntersector {
animation.autoplay &&
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group)
) {
console.warn('play animation:', animation);
//console.warn('play animation:', animation);
animation.play();
}
}

86
src/components/chat/bubbles.ts

@ -40,8 +40,9 @@ import AudioElement from "../audio"; @@ -40,8 +40,9 @@ import AudioElement from "../audio";
import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { FocusDirection } from "../../helpers/fastSmoothScroll";
import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent } from "../../hooks/useHeavyAnimationCheck";
import { fastRaf } from "../../helpers/schedulers";
import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise";
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
@ -295,30 +296,35 @@ export default class ChatBubbles { @@ -295,30 +296,35 @@ export default class ChatBubbles {
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {
const {peerId, mids} = e.detail;
(mids as number[]).forEach(mid => {
/* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve();
promise.then(() => {
}); */
this.needUpdate.forEachReverse((obj, idx) => {
if(obj.replyMid === mid, obj.replyToPeerId === peerId) {
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
//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!
const middleware = this.getMiddleware();
getHeavyAnimationPromise().then(() => {
if(!middleware()) return;
(mids as number[]).forEach(mid => {
/* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve();
promise.then(() => {
}); */
this.needUpdate.forEachReverse((obj, idx) => {
if(obj.replyMid === mid, obj.replyToPeerId === peerId) {
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
//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 { @@ -1421,7 +1427,7 @@ export default class ChatBubbles {
}
public setMessagesQueuePromise() {
if(this.messagesQueuePromise) return;
if(this.messagesQueuePromise || !this.messagesQueue.length) return;
this.messagesQueuePromise = new Promise((resolve, reject) => {
setTimeout(() => {
@ -1525,7 +1531,7 @@ export default class ChatBubbles { @@ -1525,7 +1531,7 @@ export default class ChatBubbles {
const peerId = this.peerId;
// * 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');
messageDiv.classList.add('message');
@ -1553,7 +1559,7 @@ export default class ChatBubbles { @@ -1553,7 +1559,7 @@ export default class ChatBubbles {
}
}
} else {
const save = ['is-highlighted'];
const save = ['is-highlighted', 'zoom-fade'];
const wasClassNames = bubble.className.split(' ');
const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c)));
bubble.className = classNames.join(' ');
@ -1561,9 +1567,11 @@ export default class ChatBubbles { @@ -1561,9 +1567,11 @@ export default class ChatBubbles {
bubbleContainer = bubble.lastElementChild as HTMLDivElement;
bubbleContainer.innerHTML = '';
//bubbleContainer.style.marginBottom = '';
const animationDelay = bubbleContainer.style.animationDelay;
bubbleContainer.style.cssText = '';
bubbleContainer.style.animationDelay = animationDelay;
if(bubble == this.firstUnreadBubble) {
if(bubble === this.firstUnreadBubble) {
bubble.classList.add('is-first-unread');
}
@ -2514,7 +2522,9 @@ export default class ChatBubbles { @@ -2514,7 +2522,9 @@ export default class ChatBubbles {
////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) => {
this.log.error('getHistory error:', err);
return false;
@ -2533,7 +2543,9 @@ export default class ChatBubbles { @@ -2533,7 +2543,9 @@ export default class ChatBubbles {
cached = true;
this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad);
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 this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true);
}
@ -2544,21 +2556,33 @@ export default class ChatBubbles { @@ -2544,21 +2556,33 @@ export default class ChatBubbles {
waitPromise.then(() => {
if(rootScope.settings.animationsEnabled) {
const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid));
const animationPromise = deferredPromise<void>();
let lastMsDelay = 0;
mids.forEach((mid, idx) => {
const bubble = this.bubbles[mid];
lastMsDelay = ((idx || 0.1) * 10);
//if(idx || isSafari) {
// ! 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.addEventListener('animationend', () => {
bubble.style.animationDelay = '';
bubble.classList.remove('zoom-fade');
if(idx === (mids.length - 1)) {
animationPromise.resolve();
}
}, {once: true});
//this.log('supa', bubble);
});
if(mids.length) {
dispatchHeavyAnimationEvent(animationPromise, lastMsDelay);
}
}
setTimeout(() => {

5
src/helpers/animation.ts

@ -19,6 +19,7 @@ export function cancelAnimationByKey(key: AnimationInstanceKey) { @@ -19,6 +19,7 @@ export function cancelAnimationByKey(key: AnimationInstanceKey) {
const instance = getAnimationInstance(key);
if(instance) {
instance.isCancelled = true;
instance.deferred.resolve();
instances.delete(key);
}
}
@ -31,7 +32,9 @@ export function animateSingle(tick: Function, key: AnimationInstanceKey, instanc @@ -31,7 +32,9 @@ export function animateSingle(tick: Function, key: AnimationInstanceKey, instanc
}
fastRaf(() => {
if(instance.isCancelled) return;
if(instance.isCancelled) {
return;
}
if(tick()) {
animateSingle(tick, key, instance);

2
src/helpers/fastSmoothScroll.ts

@ -95,7 +95,7 @@ function scrollWithJs( @@ -95,7 +95,7 @@ function scrollWithJs(
switch(position) {
case 'start':
path = (elementPosition - margin) - scrollPosition;
path = elementPosition - margin;
break;
case 'end':
//path = (elementTop + elementHeight + margin) - containerHeight;

36
src/hooks/useHeavyAnimationCheck.ts

@ -4,31 +4,43 @@ @@ -4,31 +4,43 @@
import { AnyToVoidFunction } from '../types';
import ListenerSetter from '../helpers/listenerSetter';
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
import { pause } from '../helpers/schedulers';
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
let isAnimating = false;
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) {
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));
isAnimating = true;
lastAnimationPromise = promise;
const promises = [
timeout !== undefined ? pause(timeout) : undefined,
promise.finally(() => {})
].filter(Boolean);
promise.then(() => {
if(lastAnimationPromise !== promise) {
return;
}
const perf = performance.now();
Promise.race(promises).then(() => {
--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();
isAnimating = false;
document.dispatchEvent(new Event(ANIMATION_END_EVENT));
heavyAnimationPromise.resolve();
console.log('dispatchHeavyAnimationEvent: end');
}
});
return heavyAnimationPromise;

6
src/scss/partials/_chatBubble.scss

@ -126,7 +126,7 @@ $bubble-margin: .25rem; @@ -126,7 +126,7 @@ $bubble-margin: .25rem;
&.is-highlighted, &.is-selected {
&:after {
top: #{2rem + $bubble-margin} !important;
top: calc(#{$bubble-margin / 2} + 30px);
}
}
}
@ -370,7 +370,7 @@ $bubble-margin: .25rem; @@ -370,7 +370,7 @@ $bubble-margin: .25rem;
&.is-group-last {
margin-bottom: #{$bubble-margin * 2};
&:before, &:after {
&:after {
bottom: -#{$bubble-margin};
}
@ -386,7 +386,7 @@ $bubble-margin: .25rem; @@ -386,7 +386,7 @@ $bubble-margin: .25rem;
}
&.is-group-first {
&:before, &:after {
&:after {
top: -#{$bubble-margin};
}
}

Loading…
Cancel
Save