tweb/src/helpers/scrollSaver.ts

176 lines
5.8 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import Scrollable from "../components/scrollable";
import { MOUNT_CLASS_TO } from "../config/debug";
import { IS_SAFARI } from "../environment/userAgent";
import getVisibleRect from "./dom/getVisibleRect";
import reflowScrollableElement from "./dom/reflowScrollableElement";
export default class ScrollSaver {
private scrollHeight: number;
// private scrollHeightMinusTop: number;
private scrollTop: number;
private clientHeight: number;
private anchor: HTMLElement;
private rect: DOMRect;
/**
*
* @param scrollable to reset scroll position and direction
* @param reverse true means top
*/
constructor(
private scrollable: Scrollable,
private query: string,
private reverse: boolean
) {
}
private get container() {
return this.scrollable.container;
}
public getSaved() {
return {
scrollHeight: this.scrollHeight,
scrollTop: this.scrollTop,
clientHeight: this.clientHeight
};
}
public findAnchor() {
const {container} = this;
const containerRect = container.getBoundingClientRect();
const bubbles = Array.from(container.querySelectorAll(this.query)) as HTMLElement[];
let rect: DOMRect, anchor: HTMLElement;
for(const bubble of bubbles) {
const elementRect = bubble.getBoundingClientRect();
const visibleRect = getVisibleRect(bubble, container, undefined, elementRect, containerRect);
if(visibleRect) {
rect = elementRect;
anchor = bubble;
// break; // find first
} else if(anchor) { // find last
break;
}
}
return {rect, anchor};
}
public findAndSetAnchor() {
const {rect, anchor} = this.findAnchor();
this.rect = rect;
this.anchor = anchor;
}
public save() {
this.findAndSetAnchor();
// console.warn('scroll save', this.anchor, this.rect);
this._save();
}
public _save() {
const {scrollTop, scrollHeight, clientHeight} = this.container;
//previousScrollHeight = scrollHeight;
//previousScrollHeight = scrollHeight + padding;
this.scrollHeight = scrollHeight;
this.scrollTop = scrollTop;
this.clientHeight = clientHeight;
// this.scrollHeightMinusTop = this.reverse ? scrollHeight - scrollTop : scrollTop;
//this.chatInner.style.paddingTop = padding + 'px';
/* if(reverse) {
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
} else {
previousScrollHeightMinusTop = scrollTop;
} */
}
private onRestore(useReflow?: boolean) {
if(IS_SAFARI && useReflow/* && !isAppleMobile */) { // * fix blinking and jumping
reflowScrollableElement(this.container);
}
}
private setScrollTop(newScrollTop: number, useReflow?: boolean) {
// touchSupport for safari iOS
//isTouchSupported && isApple && (container.container.style.overflow = 'hidden');
this.scrollable.setScrollTopSilently(this.scrollTop = newScrollTop);
//container.scrollTop = scrollHeight;
//isTouchSupported && isApple && (container.container.style.overflow = '');
this.onRestore(useReflow);
}
public restore(useReflow?: boolean) {
const {scrollTop, scrollHeight} = this.scrollable;
this.scrollHeight = scrollHeight;
// if(!this.anchor.parentElement) { // fallback to old method if element has disappeared (e.g. edited)
// this._restore(useReflow);
// return;
// }
if(!this.anchor.parentElement) { // try to find new anchor
this.findAndSetAnchor();
}
const rect = this.rect;
const newRect = this.anchor.getBoundingClientRect();
const diff = newRect.bottom - rect.bottom;
this.setScrollTop(scrollTop + diff, useReflow);
// console.warn('scroll restore', rect, diff, newRect);
}
// public _restore(useReflow?: boolean) {
// const {scrollHeightMinusTop: previousScrollHeightMinusTop, scrollable} = this;
// // if(previousScrollHeightMinusTop === undefined) {
// // throw new Error('scroll was not saved');
// // }
// // const scrollHeight = container.scrollHeight;
// const scrollHeight = this.scrollHeight;
// // if(scrollHeight === this.scrollHeight) {
// // return;
// // }
// // this.scrollHeight = scrollHeight;
// /* const scrollHeight = container.scrollHeight;
// const addedHeight = scrollHeight - previousScrollHeight;
// this.chatInner.style.paddingTop = (10000 - addedHeight) + 'px'; */
// /* const scrollHeight = scrollHeight;
// const addedHeight = scrollHeight - previousScrollHeight;
// this.chatInner.style.paddingTop = (padding - addedHeight) + 'px';
// //const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
// const newScrollTop = reverse ? scrollHeight - addedHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
// this.log('performHistoryResult: will set scrollTop',
// previousScrollHeightMinusTop, scrollHeight,
// newScrollTop, container.container.clientHeight); */
// //const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
// const newScrollTop = this.reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
// /* if(DEBUG) {
// this.log('performHistoryResult: will set up scrollTop:', newScrollTop, this.isHeavyAnimationInProgress);
// } */
// this.setScrollTop(newScrollTop, useReflow);
// /* if(DEBUG) {
// this.log('performHistoryResult: have set up scrollTop:', newScrollTop, container.scrollTop, container.scrollHeight, this.isHeavyAnimationInProgress);
// } */
// }
}
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.ScrollSaver = ScrollSaver);