Browse Source

Fix flickering date

Fix jumping scroll on slicing
master
Eduard Kuzmenko 2 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"; @@ -43,7 +43,7 @@ import PollElement from "../poll";
import AudioElement from "../audio";
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 { FocusDirection } from "../../helpers/fastSmoothScroll";
import { FocusDirection, ScrollStartCallbackDimensions } from "../../helpers/fastSmoothScroll";
import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck";
import { fastRaf, fastRafPromise } from "../../helpers/schedulers";
import { deferredPromise } from "../../helpers/cancellablePromise";
@ -119,6 +119,7 @@ type GenerateLocalMessageType<IsService> = IsService extends true ? Message.mess @@ -119,6 +119,7 @@ type GenerateLocalMessageType<IsService> = IsService extends true ? Message.mess
const SPONSORED_MESSAGE_ID_OFFSET = 1;
const STICKY_OFFSET = 3;
const SCROLLED_DOWN_THRESHOLD = 300;
export default class ChatBubbles {
public bubblesContainer: HTMLDivElement;
@ -207,7 +208,8 @@ export default class ChatBubbles { @@ -207,7 +208,8 @@ export default class ChatBubbles {
private hoverBubble: HTMLElement;
private hoverReaction: HTMLElement;
private sliceViewportDebounced: DebounceReturnType<ChatBubbles['sliceViewport']>;
resizeObserver: ResizeObserver;
private resizeObserver: ResizeObserver;
private willScrollOnLoad: boolean;
// private reactions: Map<number, ReactionsElement>;
@ -1758,21 +1760,38 @@ export default class ChatBubbles { @@ -1758,21 +1760,38 @@ export default class ChatBubbles {
}
}
public onScroll = () => {
public onScroll = (ignoreHeavyAnimation?: boolean, scrollDimensions?: ScrollStartCallbackDimensions) => {
//return;
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
if(this.isHeavyAnimationInProgress && this.scrolledDown) {
if(this.isHeavyAnimationInProgress) {
if(this.sliceViewportDebounced) {
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');
const distanceToEnd = this.scrollable.getDistanceToEnd();
if(/* !IS_TOUCH_SUPPORTED && */this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) {
if(scrollDimensions && scrollDimensions.distanceToEnd < SCROLLED_DOWN_THRESHOLD && this.scrolledDown) {
return;
}
const distanceToEnd = scrollDimensions?.distanceToEnd ?? this.scrollable.getDistanceToEnd();
if(/* !IS_TOUCH_SUPPORTED && */(this.scrollable.lastScrollDirection !== 0 && distanceToEnd > 0) || scrollDimensions) {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
@ -1782,26 +1801,16 @@ export default class ChatBubbles { @@ -1782,26 +1801,16 @@ export default class ChatBubbles {
this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
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.scrolledDown = true;
} else if(this.bubblesContainer.classList.contains('scrolled-down')) {
this.bubblesContainer.classList.remove('scrolled-down');
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() {
@ -1917,6 +1926,7 @@ export default class ChatBubbles { @@ -1917,6 +1926,7 @@ export default class ChatBubbles {
return;
}
this.scrollable.ignoreNextScrollEvent();
if(permanent && this.chat.selection.isSelecting) {
this.chat.selection.deleteSelectedMids(this.peerId, mids);
}
@ -2080,7 +2090,11 @@ export default class ChatBubbles { @@ -2080,7 +2090,11 @@ export default class ChatBubbles {
const diff = rowsWrapperHeight - 54;
return rect.height + diff; */
} : 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
@ -2409,7 +2423,8 @@ export default class ChatBubbles { @@ -2409,7 +2423,8 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, false);
} else if(topMessage && !isJump) {
//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);
}
@ -2473,6 +2488,12 @@ export default class ChatBubbles { @@ -2473,6 +2488,12 @@ export default class ChatBubbles {
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']>;
if(!savedPosition) {
result = this.getHistory(lastMsgId, true, isJump, additionMsgId);
@ -2547,11 +2568,10 @@ export default class ChatBubbles { @@ -2547,11 +2568,10 @@ export default class ChatBubbles {
const distance = savedPosition.top - top;
scrollable.scrollTop += distance;
} */
} else if((topMessage && isJump) || isTarget) {
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
if(!fromUp && samePeer) {
} else if(haveToScrollToBubble) {
if(scrollFromDown) {
scrollable.setScrollTopSilently(99999);
} else if(fromUp/* && (samePeer || forwardingUnread) */) {
} else if(scrollFromUp) {
scrollable.setScrollTopSilently(0);
}
@ -2564,7 +2584,7 @@ export default class ChatBubbles { @@ -2564,7 +2584,7 @@ export default class ChatBubbles {
// ! sometimes there can be no bubble
if(bubble) {
this.scrollToBubble(bubble, followingUnread ? 'start' : 'center', !samePeer ? FocusDirection.Static : undefined);
if(!followingUnread) {
if(!followingUnread && isTarget) {
this.highlightBubble(bubble);
}
}
@ -3971,7 +3991,7 @@ export default class ChatBubbles { @@ -3971,7 +3991,7 @@ export default class ChatBubbles {
if(this.getRenderedLength() && !this.chat.setPeerPromise) {
const viewportSlice = this.getViewportSlice();
this.deleteViewportSlice(viewportSlice);
this.deleteViewportSlice(viewportSlice, true);
}
scrollSaver.save();
@ -4032,12 +4052,20 @@ export default class ChatBubbles { @@ -4032,12 +4052,20 @@ export default class ChatBubbles {
const dateMessage = this.dateMessages[timestamp];
dateMessage.div.classList.add('is-sticky');
} */
const middleware = this.getMiddleware();
setTimeout(() => {
const callback = () => {
if(!middleware()) return;
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 { @@ -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 invisible = invisibleTop.concat(invisibleBottom);
if(!invisible.length) {
return;
}
if(invisibleTop.length) this.setLoaded('top', false);
if(invisibleBottom.length) this.setLoaded('bottom', false);
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);
if(scrollSaver) {
scrollSaver.restore();
} else if(invisibleTop.length) {
this.scrollable.lastScrollPosition = this.scrollable.scrollTop;
}
}
public sliceViewport(ignoreHeavyAnimation?: boolean) {
@ -4511,7 +4557,7 @@ export default class ChatBubbles { @@ -4511,7 +4557,7 @@ export default class ChatBubbles {
const slice = this.getViewportSlice();
// if(IS_SAFARI) slice.invisibleTop = [];
this.deleteViewportSlice(slice);
// scrollSaver.restore(false);
// scrollSaver.restore();
}
private setLoaded(side: SliceSides, value: boolean, checkPlaceholders = true) {
@ -4641,7 +4687,7 @@ export default class ChatBubbles { @@ -4641,7 +4687,7 @@ export default class ChatBubbles {
const isBroadcast = this.appPeersManager.isBroadcast(peerId);
//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 realLoadCount = isBroadcast ? 20 : (Object.keys(this.bubbles).length > 0 ? Math.max(35, pageCount) : pageCount);
//const realLoadCount = pageCount;//const realLoadCount = 50;
@ -4753,7 +4799,11 @@ export default class ChatBubbles { @@ -4753,7 +4799,11 @@ export default class ChatBubbles {
}
if(justLoad) {
this.scrollable.onScroll(); // нужно делать из-за ранней прогрузки
// нужно делать из-за ранней прогрузки
this.scrollable.onScroll();
// fastRaf(() => {
// this.scrollable.checkForTriggers();
// });
return true;
}
//console.timeEnd('appImManager call getHistory');
@ -4772,6 +4822,8 @@ export default class ChatBubbles { @@ -4772,6 +4822,8 @@ export default class ChatBubbles {
cached = false;
promise = processPromise(result);
} else if(justLoad) {
// нужно делать из-за ранней прогрузки
this.scrollable.onScroll();
return null;
} else {
cached = true;

11
src/components/scrollable.ts

@ -179,7 +179,8 @@ export class ScrollableBase { @@ -179,7 +179,8 @@ export class ScrollableBase {
this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top
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();
}
@ -280,6 +281,12 @@ export default class Scrollable extends ScrollableBase { @@ -280,6 +281,12 @@ export default class Scrollable extends ScrollableBase {
public setScrollTopSilently(value: number) {
this.lastScrollPosition = value;
this.ignoreNextScrollEvent();
this.scrollTop = value;
}
public ignoreNextScrollEvent() {
if(this.removeHeavyAnimationListener) {
this.removeScrollListener();
this.container.addEventListener('scroll', (e) => {
@ -287,8 +294,6 @@ export default class Scrollable extends ScrollableBase { @@ -287,8 +294,6 @@ export default class Scrollable extends ScrollableBase {
this.addScrollListener();
}, {capture: true, passive: false, once: true});
}
this.scrollTop = value;
}
get scrollHeight() {

25
src/helpers/fastSmoothScroll.ts

@ -24,6 +24,15 @@ export enum FocusDirection { @@ -24,6 +24,15 @@ export enum FocusDirection {
};
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 = {
container: HTMLElement,
@ -35,7 +44,8 @@ export type ScrollOptions = { @@ -35,7 +44,8 @@ export type ScrollOptions = {
forceDuration?: number,
axis?: 'x' | 'y',
getNormalSize?: ScrollGetNormalSizeCallback,
fallbackToElementStartWhenCentering?: HTMLElement
fallbackToElementStartWhenCentering?: HTMLElement,
startCallback?: (dimensions: ScrollStartCallbackDimensions) => void
};
export default function fastSmoothScroll(options: ScrollOptions) {
@ -266,6 +276,19 @@ function scrollWithJs(options: ScrollOptions): Promise<void> { @@ -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);
}

Loading…
Cancel
Save