Browse Source

Fix flickering date

Fix jumping scroll on slicing
master
Eduard Kuzmenko 3 years ago
parent
commit
731cbd5e7f
  1. 126
      src/components/chat/bubbles.ts
  2. 11
      src/components/scrollable.ts
  3. 25
      src/helpers/fastSmoothScroll.ts

126
src/components/chat/bubbles.ts

@ -43,7 +43,7 @@ import PollElement from "../poll";
import AudioElement from "../audio"; import AudioElement from "../audio";
import { ChatInvite, Message, MessageEntity, MessageMedia, MessageReplyHeader, Photo, PhotoSize, ReactionCount, ReplyMarkup, SponsoredMessage, Update, WebPage } from "../../layer"; import { ChatInvite, Message, MessageEntity, MessageMedia, MessageReplyHeader, Photo, PhotoSize, ReactionCount, ReplyMarkup, SponsoredMessage, Update, WebPage } from "../../layer";
import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { FocusDirection } from "../../helpers/fastSmoothScroll"; import { FocusDirection, ScrollStartCallbackDimensions } from "../../helpers/fastSmoothScroll";
import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck"; import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck";
import { fastRaf, fastRafPromise } from "../../helpers/schedulers"; import { fastRaf, fastRafPromise } from "../../helpers/schedulers";
import { deferredPromise } from "../../helpers/cancellablePromise"; import { deferredPromise } from "../../helpers/cancellablePromise";
@ -119,6 +119,7 @@ type GenerateLocalMessageType<IsService> = IsService extends true ? Message.mess
const SPONSORED_MESSAGE_ID_OFFSET = 1; const SPONSORED_MESSAGE_ID_OFFSET = 1;
const STICKY_OFFSET = 3; const STICKY_OFFSET = 3;
const SCROLLED_DOWN_THRESHOLD = 300;
export default class ChatBubbles { export default class ChatBubbles {
public bubblesContainer: HTMLDivElement; public bubblesContainer: HTMLDivElement;
@ -207,7 +208,8 @@ export default class ChatBubbles {
private hoverBubble: HTMLElement; private hoverBubble: HTMLElement;
private hoverReaction: HTMLElement; private hoverReaction: HTMLElement;
private sliceViewportDebounced: DebounceReturnType<ChatBubbles['sliceViewport']>; private sliceViewportDebounced: DebounceReturnType<ChatBubbles['sliceViewport']>;
resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
private willScrollOnLoad: boolean;
// private reactions: Map<number, ReactionsElement>; // private reactions: Map<number, ReactionsElement>;
@ -1758,21 +1760,38 @@ export default class ChatBubbles {
} }
} }
public onScroll = () => { public onScroll = (ignoreHeavyAnimation?: boolean, scrollDimensions?: ScrollStartCallbackDimensions) => {
//return; //return;
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз if(this.isHeavyAnimationInProgress) {
if(this.isHeavyAnimationInProgress && this.scrolledDown) {
if(this.sliceViewportDebounced) { if(this.sliceViewportDebounced) {
this.sliceViewportDebounced.clearTimeout(); this.sliceViewportDebounced.clearTimeout();
} }
return; // * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
if(this.scrolledDown && !ignoreHeavyAnimation) {
return;
}
} else {
if(this.chat.topbar.pinnedMessage) {
this.chat.topbar.pinnedMessage.setCorrectIndexThrottled(this.scrollable.lastScrollDirection);
}
if(this.sliceViewportDebounced) {
this.sliceViewportDebounced();
}
this.setStickyDateManually();
} }
//lottieLoader.checkAnimations(false, 'chat'); //lottieLoader.checkAnimations(false, 'chat');
const distanceToEnd = this.scrollable.getDistanceToEnd(); if(scrollDimensions && scrollDimensions.distanceToEnd < SCROLLED_DOWN_THRESHOLD && this.scrolledDown) {
if(/* !IS_TOUCH_SUPPORTED && */this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) { return;
}
const distanceToEnd = scrollDimensions?.distanceToEnd ?? this.scrollable.getDistanceToEnd();
if(/* !IS_TOUCH_SUPPORTED && */(this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) || scrollDimensions) {
if(this.isScrollingTimeout) { if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout); clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) { } else if(!this.chatInner.classList.contains('is-scrolling')) {
@ -1782,26 +1801,16 @@ export default class ChatBubbles {
this.isScrollingTimeout = window.setTimeout(() => { this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling'); this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0; this.isScrollingTimeout = 0;
}, 1350); }, 1350 + (scrollDimensions?.duration ?? 0));
} }
if(distanceToEnd < 300 && (this.scrollable.loadedAll.bottom || this.chat.setPeerPromise || !this.peerId)) { if(distanceToEnd < SCROLLED_DOWN_THRESHOLD && (this.scrollable.loadedAll.bottom || this.chat.setPeerPromise || !this.peerId)) {
this.bubblesContainer.classList.add('scrolled-down'); this.bubblesContainer.classList.add('scrolled-down');
this.scrolledDown = true; this.scrolledDown = true;
} else if(this.bubblesContainer.classList.contains('scrolled-down')) { } else if(this.bubblesContainer.classList.contains('scrolled-down')) {
this.bubblesContainer.classList.remove('scrolled-down'); this.bubblesContainer.classList.remove('scrolled-down');
this.scrolledDown = false; this.scrolledDown = false;
} }
if(this.chat.topbar.pinnedMessage) {
this.chat.topbar.pinnedMessage.setCorrectIndexThrottled(this.scrollable.lastScrollDirection);
}
if(this.sliceViewportDebounced) {
this.sliceViewportDebounced();
}
this.setStickyDateManually();
}; };
public setScroll() { public setScroll() {
@ -1917,6 +1926,7 @@ export default class ChatBubbles {
return; return;
} }
this.scrollable.ignoreNextScrollEvent();
if(permanent && this.chat.selection.isSelecting) { if(permanent && this.chat.selection.isSelecting) {
this.chat.selection.deleteSelectedMids(this.peerId, mids); this.chat.selection.deleteSelectedMids(this.peerId, mids);
} }
@ -2080,7 +2090,11 @@ export default class ChatBubbles {
const diff = rowsWrapperHeight - 54; const diff = rowsWrapperHeight - 54;
return rect.height + diff; */ return rect.height + diff; */
} : undefined, } : undefined,
fallbackToElementStartWhenCentering fallbackToElementStartWhenCentering,
startCallback: (dimensions) => {
// this.onScroll(true, this.scrolledDown && dimensions.distanceToEnd <= SCROLLED_DOWN_THRESHOLD ? undefined : dimensions);
this.onScroll(true, dimensions);
}
}); });
// fix flickering date when opening unread chat and focusing message // fix flickering date when opening unread chat and focusing message
@ -2409,7 +2423,8 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, false); this.chat.dispatchEvent('setPeer', lastMsgId, false);
} else if(topMessage && !isJump) { } else if(topMessage && !isJump) {
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); //this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
scrollable.setScrollTopSilently(scrollable.scrollHeight); // scrollable.setScrollTopSilently(scrollable.scrollHeight);
this.scrollToEnd();
this.chat.dispatchEvent('setPeer', lastMsgId, true); this.chat.dispatchEvent('setPeer', lastMsgId, true);
} }
@ -2473,6 +2488,12 @@ export default class ChatBubbles {
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
const haveToScrollToBubble = (topMessage && (isJump || samePeer)) || isTarget;
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
const scrollFromDown = !fromUp && samePeer;
const scrollFromUp = !scrollFromDown && fromUp/* && (samePeer || forwardingUnread) */;
this.willScrollOnLoad = scrollFromDown || scrollFromUp;
let result: ReturnType<ChatBubbles['getHistory']>; let result: ReturnType<ChatBubbles['getHistory']>;
if(!savedPosition) { if(!savedPosition) {
result = this.getHistory(lastMsgId, true, isJump, additionMsgId); result = this.getHistory(lastMsgId, true, isJump, additionMsgId);
@ -2547,11 +2568,10 @@ export default class ChatBubbles {
const distance = savedPosition.top - top; const distance = savedPosition.top - top;
scrollable.scrollTop += distance; scrollable.scrollTop += distance;
} */ } */
} else if((topMessage && isJump) || isTarget) { } else if(haveToScrollToBubble) {
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0); if(scrollFromDown) {
if(!fromUp && samePeer) {
scrollable.setScrollTopSilently(99999); scrollable.setScrollTopSilently(99999);
} else if(fromUp/* && (samePeer || forwardingUnread) */) { } else if(scrollFromUp) {
scrollable.setScrollTopSilently(0); scrollable.setScrollTopSilently(0);
} }
@ -2564,7 +2584,7 @@ export default class ChatBubbles {
// ! sometimes there can be no bubble // ! sometimes there can be no bubble
if(bubble) { if(bubble) {
this.scrollToBubble(bubble, followingUnread ? 'start' : 'center', !samePeer ? FocusDirection.Static : undefined); this.scrollToBubble(bubble, followingUnread ? 'start' : 'center', !samePeer ? FocusDirection.Static : undefined);
if(!followingUnread) { if(!followingUnread && isTarget) {
this.highlightBubble(bubble); this.highlightBubble(bubble);
} }
} }
@ -3971,7 +3991,7 @@ export default class ChatBubbles {
if(this.getRenderedLength() && !this.chat.setPeerPromise) { if(this.getRenderedLength() && !this.chat.setPeerPromise) {
const viewportSlice = this.getViewportSlice(); const viewportSlice = this.getViewportSlice();
this.deleteViewportSlice(viewportSlice); this.deleteViewportSlice(viewportSlice, true);
} }
scrollSaver.save(); scrollSaver.save();
@ -4032,12 +4052,20 @@ export default class ChatBubbles {
const dateMessage = this.dateMessages[timestamp]; const dateMessage = this.dateMessages[timestamp];
dateMessage.div.classList.add('is-sticky'); dateMessage.div.classList.add('is-sticky');
} */ } */
const middleware = this.getMiddleware(); const middleware = this.getMiddleware();
setTimeout(() => { const callback = () => {
if(!middleware()) return; if(!middleware()) return;
this.bubblesContainer.classList.add('has-sticky-dates'); this.bubblesContainer.classList.add('has-sticky-dates');
}, 600); };
if(this.willScrollOnLoad) {
callback();
} else {
setTimeout(callback, 600);
}
} else {
this.willScrollOnLoad = undefined;
} }
} }
@ -4489,15 +4517,33 @@ export default class ChatBubbles {
}); });
} }
public deleteViewportSlice(slice: ReturnType<ChatBubbles['getViewportSlice']>) { public deleteViewportSlice(slice: ReturnType<ChatBubbles['getViewportSlice']>, ignoreScrollSaving?: boolean) {
// return;
const {invisibleTop, invisibleBottom} = slice; const {invisibleTop, invisibleBottom} = slice;
const invisible = invisibleTop.concat(invisibleBottom); const invisible = invisibleTop.concat(invisibleBottom);
if(!invisible.length) {
return;
}
if(invisibleTop.length) this.setLoaded('top', false); if(invisibleTop.length) this.setLoaded('top', false);
if(invisibleBottom.length) this.setLoaded('bottom', false); if(invisibleBottom.length) this.setLoaded('bottom', false);
const mids = invisible.map(({element}) => +element.dataset.mid); const mids = invisible.map(({element}) => +element.dataset.mid);
let scrollSaver: ScrollSaver;
if(!!invisibleTop.length !== !!invisibleBottom.length && !ignoreScrollSaving) {
scrollSaver = new ScrollSaver(this.scrollable, !!invisibleTop.length);
scrollSaver.save();
}
this.deleteMessagesByIds(mids, false, true); this.deleteMessagesByIds(mids, false, true);
if(scrollSaver) {
scrollSaver.restore();
} else if(invisibleTop.length) {
this.scrollable.lastScrollPosition = this.scrollable.scrollTop;
}
} }
public sliceViewport(ignoreHeavyAnimation?: boolean) { public sliceViewport(ignoreHeavyAnimation?: boolean) {
@ -4511,7 +4557,7 @@ export default class ChatBubbles {
const slice = this.getViewportSlice(); const slice = this.getViewportSlice();
// if(IS_SAFARI) slice.invisibleTop = []; // if(IS_SAFARI) slice.invisibleTop = [];
this.deleteViewportSlice(slice); this.deleteViewportSlice(slice);
// scrollSaver.restore(false); // scrollSaver.restore();
} }
private setLoaded(side: SliceSides, value: boolean, checkPlaceholders = true) { private setLoaded(side: SliceSides, value: boolean, checkPlaceholders = true) {
@ -4641,7 +4687,7 @@ export default class ChatBubbles {
const isBroadcast = this.appPeersManager.isBroadcast(peerId); const isBroadcast = this.appPeersManager.isBroadcast(peerId);
//console.time('appImManager call getHistory'); //console.time('appImManager call getHistory');
const pageCount = Math.min(30, windowSize.height / 48/* * 1.25 */ | 0); const pageCount = Math.min(30, windowSize.height / 40/* * 1.25 */ | 0);
//const loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; //const loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount;
const realLoadCount = isBroadcast ? 20 : (Object.keys(this.bubbles).length > 0 ? Math.max(35, pageCount) : pageCount); const realLoadCount = isBroadcast ? 20 : (Object.keys(this.bubbles).length > 0 ? Math.max(35, pageCount) : pageCount);
//const realLoadCount = pageCount;//const realLoadCount = 50; //const realLoadCount = pageCount;//const realLoadCount = 50;
@ -4753,7 +4799,11 @@ export default class ChatBubbles {
} }
if(justLoad) { if(justLoad) {
this.scrollable.onScroll(); // нужно делать из-за ранней прогрузки // нужно делать из-за ранней прогрузки
this.scrollable.onScroll();
// fastRaf(() => {
// this.scrollable.checkForTriggers();
// });
return true; return true;
} }
//console.timeEnd('appImManager call getHistory'); //console.timeEnd('appImManager call getHistory');
@ -4772,6 +4822,8 @@ export default class ChatBubbles {
cached = false; cached = false;
promise = processPromise(result); promise = processPromise(result);
} else if(justLoad) { } else if(justLoad) {
// нужно делать из-за ранней прогрузки
this.scrollable.onScroll();
return null; return null;
} else { } else {
cached = true; cached = true;

11
src/components/scrollable.ts

@ -179,7 +179,8 @@ export class ScrollableBase {
this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollPosition = scrollPosition; this.lastScrollPosition = scrollPosition;
if(this.onAdditionalScroll && this.lastScrollDirection !== 0) { // lastScrollDirection check is useless here, every callback should decide on its own
if(this.onAdditionalScroll/* && this.lastScrollDirection !== 0 */) {
this.onAdditionalScroll(); this.onAdditionalScroll();
} }
@ -280,6 +281,12 @@ export default class Scrollable extends ScrollableBase {
public setScrollTopSilently(value: number) { public setScrollTopSilently(value: number) {
this.lastScrollPosition = value; this.lastScrollPosition = value;
this.ignoreNextScrollEvent();
this.scrollTop = value;
}
public ignoreNextScrollEvent() {
if(this.removeHeavyAnimationListener) { if(this.removeHeavyAnimationListener) {
this.removeScrollListener(); this.removeScrollListener();
this.container.addEventListener('scroll', (e) => { this.container.addEventListener('scroll', (e) => {
@ -287,8 +294,6 @@ export default class Scrollable extends ScrollableBase {
this.addScrollListener(); this.addScrollListener();
}, {capture: true, passive: false, once: true}); }, {capture: true, passive: false, once: true});
} }
this.scrollTop = value;
} }
get scrollHeight() { get scrollHeight() {

25
src/helpers/fastSmoothScroll.ts

@ -24,6 +24,15 @@ export enum FocusDirection {
}; };
export type ScrollGetNormalSizeCallback = (options: {rect: DOMRect}) => number; export type ScrollGetNormalSizeCallback = (options: {rect: DOMRect}) => number;
export type ScrollStartCallbackDimensions = {
scrollSize: number,
scrollPosition: number,
distanceToEnd: number,
path: number,
duration: number,
containerRect: DOMRect,
elementRect: DOMRect,
};
export type ScrollOptions = { export type ScrollOptions = {
container: HTMLElement, container: HTMLElement,
@ -35,7 +44,8 @@ export type ScrollOptions = {
forceDuration?: number, forceDuration?: number,
axis?: 'x' | 'y', axis?: 'x' | 'y',
getNormalSize?: ScrollGetNormalSizeCallback, getNormalSize?: ScrollGetNormalSizeCallback,
fallbackToElementStartWhenCentering?: HTMLElement fallbackToElementStartWhenCentering?: HTMLElement,
startCallback?: (dimensions: ScrollStartCallbackDimensions) => void
}; };
export default function fastSmoothScroll(options: ScrollOptions) { export default function fastSmoothScroll(options: ScrollOptions) {
@ -266,6 +276,19 @@ function scrollWithJs(options: ScrollOptions): Promise<void> {
}); */ }); */
if(options.startCallback) {
const distanceToEnd = scrollSize - Math.round(target + container[axis === 'y' ? 'offsetHeight' : 'offsetWidth']);
options.startCallback({
scrollSize,
scrollPosition,
distanceToEnd,
path,
duration,
containerRect,
elementRect
});
}
return animateSingle(tick, container); return animateSingle(tick, container);
} }

Loading…
Cancel
Save