scroll try #3
This commit is contained in:
parent
8873c2d3ec
commit
a5b2f27b98
@ -18,17 +18,17 @@ setTimeout(() => {
|
|||||||
export default class Scrollable {
|
export default class Scrollable {
|
||||||
public container: HTMLDivElement;
|
public container: HTMLDivElement;
|
||||||
public thumb: HTMLDivElement;
|
public thumb: HTMLDivElement;
|
||||||
|
|
||||||
public type: string;
|
public type: string;
|
||||||
public side: string;
|
public side: string;
|
||||||
public scrollType: string;
|
public scrollType: string;
|
||||||
public scrollSide: string;
|
public scrollSide: string;
|
||||||
public clientAxis: string;
|
public clientAxis: string;
|
||||||
|
|
||||||
public scrollSize = -1;
|
public scrollSize = -1;
|
||||||
public size = 0;
|
public size = 0;
|
||||||
public thumbSize = 0;
|
public thumbSize = 0;
|
||||||
|
|
||||||
public hiddenElements: {
|
public hiddenElements: {
|
||||||
up: {element: Element, height: number}[],
|
up: {element: Element, height: number}[],
|
||||||
down: {element: Element, height: number}[]
|
down: {element: Element, height: number}[]
|
||||||
@ -37,144 +37,55 @@ export default class Scrollable {
|
|||||||
down: []
|
down: []
|
||||||
};
|
};
|
||||||
public paddings = {up: 0, down: 0};
|
public paddings = {up: 0, down: 0};
|
||||||
|
|
||||||
public paddingTopDiv: HTMLDivElement;
|
public paddingTopDiv: HTMLDivElement;
|
||||||
public paddingBottomDiv: HTMLDivElement;
|
public paddingBottomDiv: HTMLDivElement;
|
||||||
|
|
||||||
public splitUp: HTMLElement;
|
public splitUp: HTMLElement;
|
||||||
|
|
||||||
public onAddedBottom: () => void = null;
|
public onAddedBottom: () => void = null;
|
||||||
|
public onScrolledTop: () => void = null;
|
||||||
|
public onScrolledBottom: () => void = null;
|
||||||
|
|
||||||
public topObserver: IntersectionObserver;
|
public topObserver: IntersectionObserver;
|
||||||
public isTopIntersecting: boolean;
|
|
||||||
public bottomObserver: IntersectionObserver;
|
public bottomObserver: IntersectionObserver;
|
||||||
public isBottomIntersecting: boolean;
|
|
||||||
|
|
||||||
public splitObserver: IntersectionObserver;
|
public splitObserver: IntersectionObserver;
|
||||||
public splitMeasure: Promise<any> = null;
|
public splitMeasure: Promise<{element: Element, height: number}[]> = null;
|
||||||
public splitMutate: Promise<any> = null;
|
public splitMeasureAdd: Promise<number> = null;
|
||||||
|
public splitMutate: Promise<void> = null;
|
||||||
|
|
||||||
constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300) {
|
constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300) {
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
this.container.classList.add('scrollable');
|
this.container.classList.add('scrollable');
|
||||||
|
|
||||||
let arr = [];
|
//let arr = [];
|
||||||
for(let i = 0.001; i < 1; i += 0.001) arr.push(i);
|
//for(let i = 0.001; i < 1; i += 0.001) arr.push(i);
|
||||||
this.topObserver = new IntersectionObserver(entries => {
|
this.topObserver = new IntersectionObserver(entries => {
|
||||||
let entry = entries[0];
|
let entry = entries[0];
|
||||||
|
|
||||||
console.log('top intersection:', entries, this.isTopIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
|
console.log('top intersection:', entries, entry.isIntersecting, entry.intersectionRatio > 0);
|
||||||
if(this.isTopIntersecting = entry.isIntersecting) {
|
if(entry.isIntersecting) {
|
||||||
this.onTopIntersection(entry);
|
//this.onTopIntersection(entry);
|
||||||
|
this.onTopIntersection(entry.intersectionRect.height);
|
||||||
|
|
||||||
|
if(this.onScrolledTop) this.onScrolledTop();
|
||||||
}
|
}
|
||||||
// console.log('top intersection end');
|
// console.log('top intersection end');
|
||||||
}, {threshold: arr});
|
}, {/* threshold: arr, */root: this.el});
|
||||||
|
|
||||||
this.bottomObserver = new IntersectionObserver(entries => {
|
this.bottomObserver = new IntersectionObserver(entries => {
|
||||||
let entry = entries[0];
|
let entry = entries[0];
|
||||||
|
|
||||||
console.log('bottom intersection:', entries, this.isBottomIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
|
console.log('bottom intersection:', entries, entry.isIntersecting, entry.intersectionRatio > 0);
|
||||||
if(this.isBottomIntersecting = entry.isIntersecting) {
|
if(entry.isIntersecting) {
|
||||||
this.onBottomIntersection(entry);
|
//this.onBottomIntersection(entry);
|
||||||
|
this.onBottomIntersection(entry.intersectionRect.height);
|
||||||
|
|
||||||
if(this.onScrolledBottom) this.onScrolledBottom();
|
if(this.onScrolledBottom) this.onScrolledBottom();
|
||||||
}
|
}
|
||||||
}, {threshold: arr});
|
}, {/* threshold: arr, */root: this.el});
|
||||||
|
|
||||||
this.splitObserver = new IntersectionObserver(entries => {
|
|
||||||
console.log('splitObserver', entries);
|
|
||||||
for(let entry of entries) { // there may be duplicates (1st - not intersecting, 2nd - intersecting)
|
|
||||||
//console.log('onscroll entry', entry.target, entry.isIntersecting, entry);
|
|
||||||
if(!entry.isIntersecting && entry.target.parentElement && entry.rootBounds) {
|
|
||||||
let child = entry.target;
|
|
||||||
//console.log('onscroll entry', entry.boundingClientRect, child, entry);
|
|
||||||
|
|
||||||
let isTop = entry.boundingClientRect.top <= 0;
|
|
||||||
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
|
|
||||||
|
|
||||||
let needHeight = this.splitOffset;
|
|
||||||
//console.log('will call measure');
|
|
||||||
if(isTop) {
|
|
||||||
this.onBottomIntersection(entry);
|
|
||||||
|
|
||||||
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
|
||||||
this.splitMeasure = fastdom.measure(() => {
|
|
||||||
let sliced: {element: Element, height: number}[] = [/* child */];
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(needHeight > 0) {
|
|
||||||
needHeight -= child.scrollHeight;
|
|
||||||
} else {
|
|
||||||
sliced.push({element: child, height: child.scrollHeight});
|
|
||||||
}
|
|
||||||
} while(child = child.previousElementSibling);
|
|
||||||
return sliced;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.splitMeasure.then(sliced => {
|
|
||||||
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
|
||||||
this.splitMutate = fastdom.mutate(() => {
|
|
||||||
let length = sliced.length;
|
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
|
||||||
let {element, height} = sliced[i];
|
|
||||||
|
|
||||||
if(!this.splitUp.contains(element)) continue;
|
|
||||||
|
|
||||||
this.paddings.up += height;
|
|
||||||
this.hiddenElements.up.push(sliced[i]);
|
|
||||||
this.splitUp.removeChild(element);
|
|
||||||
//element.parentElement.removeChild(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//console.log('onscroll sliced up', sliced);
|
|
||||||
} else if(isBottom) {
|
|
||||||
this.onTopIntersection(entry);
|
|
||||||
|
|
||||||
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
|
||||||
this.splitMeasure = fastdom.measure(() => {
|
|
||||||
let sliced: {element: Element, height: number}[] = [/* child */];
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(needHeight > 0) {
|
|
||||||
needHeight -= child.scrollHeight;
|
|
||||||
} else {
|
|
||||||
sliced.push({element: child, height: child.scrollHeight});
|
|
||||||
}
|
|
||||||
} while(child = child.nextElementSibling);
|
|
||||||
return sliced;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.splitMeasure.then(sliced => {
|
|
||||||
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
|
||||||
this.splitMutate = fastdom.mutate(() => {
|
|
||||||
let length = sliced.length;
|
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
|
||||||
let {element, height} = sliced[i];
|
|
||||||
|
|
||||||
if(!this.splitUp.contains(element)) continue;
|
|
||||||
|
|
||||||
this.paddings.down += height;
|
|
||||||
this.hiddenElements.down.unshift(sliced[i]);
|
|
||||||
this.splitUp.removeChild(element);
|
|
||||||
//element.parentElement.removeChild(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//console.log('onscroll sliced down', sliced);
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log('splitObserver', entry, entry.target, isTop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(x) {
|
if(x) {
|
||||||
this.container.classList.add('scrollable-x');
|
this.container.classList.add('scrollable-x');
|
||||||
this.type = 'width';
|
this.type = 'width';
|
||||||
@ -182,7 +93,7 @@ export default class Scrollable {
|
|||||||
this.scrollType = 'scrollWidth';
|
this.scrollType = 'scrollWidth';
|
||||||
this.scrollSide = 'scrollLeft';
|
this.scrollSide = 'scrollLeft';
|
||||||
this.clientAxis = 'clientX';
|
this.clientAxis = 'clientX';
|
||||||
|
|
||||||
let scrollHorizontally = (e: any) => {
|
let scrollHorizontally = (e: any) => {
|
||||||
e = window.event || e;
|
e = window.event || e;
|
||||||
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
|
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
|
||||||
@ -209,47 +120,47 @@ export default class Scrollable {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('no side for scroll');
|
throw new Error('no side for scroll');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.thumb = document.createElement('div');
|
this.thumb = document.createElement('div');
|
||||||
this.thumb.className = 'scrollbar-thumb';
|
this.thumb.className = 'scrollbar-thumb';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.thumb.style[this.type] = '30px';
|
this.thumb.style[this.type] = '30px';
|
||||||
|
|
||||||
// mouse scroll
|
// mouse scroll
|
||||||
let onMouseMove = (e: MouseEvent) => {
|
let onMouseMove = (e: MouseEvent) => {
|
||||||
let rect = this.thumb.getBoundingClientRect();
|
let rect = this.thumb.getBoundingClientRect();
|
||||||
|
|
||||||
let diff: number;
|
let diff: number;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
diff = e[this.clientAxis] - rect[this.side];
|
diff = e[this.clientAxis] - rect[this.side];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.container[this.scrollSide] += diff * 0.5;
|
this.container[this.scrollSide] += diff * 0.5;
|
||||||
|
|
||||||
// console.log('onMouseMove', e, diff);
|
// console.log('onMouseMove', e, diff);
|
||||||
|
|
||||||
cancelEvent(e);
|
cancelEvent(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.thumb.addEventListener('mousedown', () => {
|
this.thumb.addEventListener('mousedown', () => {
|
||||||
window.addEventListener('mousemove', onMouseMove);
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
|
||||||
window.addEventListener('mouseup', () => {
|
window.addEventListener('mouseup', () => {
|
||||||
window.removeEventListener('mousemove', onMouseMove);
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg
|
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg
|
||||||
window.addEventListener('resize', this.resize.bind(this));
|
window.addEventListener('resize', this.resize.bind(this));
|
||||||
|
|
||||||
this.paddingTopDiv = document.createElement('div');
|
this.paddingTopDiv = document.createElement('div');
|
||||||
this.paddingTopDiv.classList.add('scroll-padding');
|
this.paddingTopDiv.classList.add('scroll-padding');
|
||||||
this.paddingBottomDiv = document.createElement('div');
|
this.paddingBottomDiv = document.createElement('div');
|
||||||
this.paddingBottomDiv.classList.add('scroll-padding');
|
this.paddingBottomDiv.classList.add('scroll-padding');
|
||||||
|
|
||||||
this.topObserver.observe(this.paddingTopDiv);
|
this.topObserver.observe(this.paddingTopDiv);
|
||||||
this.bottomObserver.observe(this.paddingBottomDiv);
|
this.bottomObserver.observe(this.paddingBottomDiv);
|
||||||
|
|
||||||
this.container.addEventListener('scroll', this.onScroll.bind(this));
|
this.container.addEventListener('scroll', this.onScroll.bind(this));
|
||||||
|
|
||||||
Array.from(el.children).forEach(c => this.container.append(c));
|
Array.from(el.children).forEach(c => this.container.append(c));
|
||||||
@ -258,16 +169,140 @@ export default class Scrollable {
|
|||||||
this.container.parentElement.append(this.thumb);
|
this.container.parentElement.append(this.thumb);
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public splitObserve(entries: IntersectionObserverEntry[]) {
|
||||||
|
console.log('splitObserver', entries);
|
||||||
|
for(let entry of entries) { // there may be duplicates (1st - not intersecting, 2nd - intersecting)
|
||||||
|
//console.log('onscroll entry', entry.target, entry.isIntersecting, entry);
|
||||||
|
if(!entry.target.parentElement || !entry.rootBounds) continue;
|
||||||
|
|
||||||
|
let child = entry.target;
|
||||||
|
let needHeight = this.splitOffset;
|
||||||
|
if(!entry.isIntersecting) {
|
||||||
|
let isTop = entry.boundingClientRect.top <= 0;
|
||||||
|
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
|
||||||
|
console.log('onscroll entry', isTop, isBottom, child, entry);
|
||||||
|
|
||||||
|
//console.log('will call measure');
|
||||||
|
if(isTop) { // when scrolling down
|
||||||
|
//this.onBottomIntersection(entry);
|
||||||
|
|
||||||
|
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
||||||
|
this.splitMeasure = fastdom.measure(() => {
|
||||||
|
let sliced: {element: Element, height: number}[] = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(needHeight > 0) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
} else {
|
||||||
|
sliced.push({element: child, height: child.scrollHeight});
|
||||||
|
}
|
||||||
|
} while(child = child.previousElementSibling);
|
||||||
|
return sliced;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasure.then(sliced => {
|
||||||
|
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
||||||
|
|
||||||
|
this.splitMutate = fastdom.mutate(() => {
|
||||||
|
sliced.forEachReverse((child) => {
|
||||||
|
let {element, height} = child;
|
||||||
|
if(!this.splitUp.contains(element)) return;
|
||||||
|
|
||||||
|
this.paddings.up += height;
|
||||||
|
this.hiddenElements.up.push(child);
|
||||||
|
this.splitUp.removeChild(element);
|
||||||
|
//element.parentElement.removeChild(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('onscroll sliced up', sliced);
|
||||||
|
} else if(isBottom) { // when scrolling top
|
||||||
|
//this.onTopIntersection(entry);
|
||||||
|
|
||||||
|
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
||||||
|
this.splitMeasure = fastdom.measure(() => {
|
||||||
|
let sliced: {element: Element, height: number}[] = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(needHeight > 0) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
} else {
|
||||||
|
sliced.push({element: child, height: child.scrollHeight});
|
||||||
|
}
|
||||||
|
} while(child = child.nextElementSibling);
|
||||||
|
return sliced;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasure.then(sliced => {
|
||||||
|
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
||||||
|
|
||||||
|
this.splitMutate = fastdom.mutate(() => {
|
||||||
|
sliced.forEachReverse((child) => {
|
||||||
|
let {element, height} = child;
|
||||||
|
if(!this.splitUp.contains(element)) return;
|
||||||
|
|
||||||
|
this.paddings.down += height;
|
||||||
|
this.hiddenElements.down.unshift(child);
|
||||||
|
this.splitUp.removeChild(element);
|
||||||
|
//element.parentElement.removeChild(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('onscroll sliced down', sliced);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('splitObserver', entry, entry.target, isTop);
|
||||||
|
} else {
|
||||||
|
let isTop = entry.boundingClientRect.top <= entry.rootBounds.top;
|
||||||
|
let isBottom = entry.boundingClientRect.bottom >= entry.rootBounds.bottom;
|
||||||
|
|
||||||
|
if(isTop) { // when scrolling up
|
||||||
|
if(this.splitMeasureAdd) fastdom.clear(this.splitMeasureAdd);
|
||||||
|
this.splitMeasureAdd = fastdom.measure(() => {
|
||||||
|
while(child = child.previousElementSibling) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return needHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasureAdd.then(needHeight => {
|
||||||
|
this.onTopIntersection(needHeight);
|
||||||
|
});
|
||||||
|
} else if(isBottom) { // when scrolling down
|
||||||
|
if(this.splitMeasureAdd) fastdom.clear(this.splitMeasureAdd);
|
||||||
|
this.splitMeasureAdd = fastdom.measure(() => {
|
||||||
|
while(child = child.nextElementSibling) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return needHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasureAdd.then(needHeight => {
|
||||||
|
this.onBottomIntersection(needHeight);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async resize() {
|
public async resize() {
|
||||||
//console.time('scroll resize');
|
//console.time('scroll resize');
|
||||||
|
|
||||||
await fastdom.measure(() => {
|
await fastdom.measure(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.scrollSize = this.container[this.scrollType];
|
this.scrollSize = this.container[this.scrollType];
|
||||||
|
|
||||||
let rect = this.container.getBoundingClientRect();
|
let rect = this.container.getBoundingClientRect();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.size = rect[this.type];
|
this.size = rect[this.type];
|
||||||
});
|
});
|
||||||
@ -291,26 +326,32 @@ export default class Scrollable {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.thumb.style[this.type] = this.thumbSize + 'px';
|
this.thumb.style[this.type] = this.thumbSize + 'px';
|
||||||
});
|
});
|
||||||
|
|
||||||
//console.timeEnd('scroll resize');
|
//console.timeEnd('scroll resize');
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
//console.log('onresize', thumb.style[type], thumbHeight, height);
|
//console.log('onresize', thumb.style[type], thumbHeight, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setVirtualContainer(el?: HTMLElement) {
|
public async setVirtualContainer(el?: HTMLElement) {
|
||||||
this.splitUp = el;
|
this.splitUp = el;
|
||||||
|
|
||||||
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
|
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
|
||||||
this.paddings.up = this.paddings.down = 0;
|
this.paddings.up = this.paddings.down = 0;
|
||||||
|
|
||||||
if(this.paddingTopDiv.parentElement) {
|
if(this.paddingTopDiv.parentElement) {
|
||||||
fastdom.mutate(() => {
|
fastdom.mutate(() => {
|
||||||
this.paddingTopDiv.style.height = '';
|
this.paddingTopDiv.style.height = '';
|
||||||
this.paddingBottomDiv.style.height = '';
|
this.paddingBottomDiv.style.height = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.splitObserver) {
|
||||||
|
this.splitObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.splitObserver = new IntersectionObserver((entries) => this.splitObserve(entries), {root: this.el});
|
||||||
|
|
||||||
if(el) {
|
if(el) {
|
||||||
fastdom.mutate(() => {
|
fastdom.mutate(() => {
|
||||||
el.parentElement.insertBefore(this.paddingTopDiv, el);
|
el.parentElement.insertBefore(this.paddingTopDiv, el);
|
||||||
@ -318,7 +359,7 @@ export default class Scrollable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onScroll() {
|
public async onScroll() {
|
||||||
//console.time('scroll onScroll');
|
//console.time('scroll onScroll');
|
||||||
let {value, maxValue} = await fastdom.measure(() => {
|
let {value, maxValue} = await fastdom.measure(() => {
|
||||||
@ -326,48 +367,47 @@ export default class Scrollable {
|
|||||||
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
|
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
|
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
|
||||||
let maxValue = 100 - (this.thumbSize / this.size * 100);
|
let maxValue = 100 - (this.thumbSize / this.size * 100);
|
||||||
|
|
||||||
return {value, maxValue};
|
return {value, maxValue};
|
||||||
});
|
});
|
||||||
|
|
||||||
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
|
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
|
||||||
fastdom.mutate(() => {
|
fastdom.mutate(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
|
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
|
||||||
});
|
});
|
||||||
|
|
||||||
//console.timeEnd('scroll onScroll');
|
//console.timeEnd('scroll onScroll');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onTopIntersection(entry: IntersectionObserverEntry) {
|
public async onTopIntersection(/* entry: IntersectionObserverEntry */needHeight: number) {
|
||||||
console.log('onTopIntersection');
|
console.log('onTopIntersection', needHeight, this);
|
||||||
|
|
||||||
if(this.hiddenElements.up.length && this.paddings.up) {
|
if(this.hiddenElements.up.length && this.paddings.up) {
|
||||||
let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
//let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
||||||
|
//let needHeight = entry.intersectionRect.height || await fastdom.measure(() => this.splitUp.lastElementChild.scrollHeight);
|
||||||
|
|
||||||
let fragment = document.createDocumentFragment();
|
let fragment = document.createDocumentFragment();
|
||||||
while(needHeight > 0 && this.paddings.up) {
|
while(needHeight > 0 && this.paddings.up) {
|
||||||
let child = this.hiddenElements.up.pop();
|
let child = this.hiddenElements.up.pop();
|
||||||
|
|
||||||
// console.log('top returning from hidden', child);
|
// console.log('top returning from hidden', child);
|
||||||
|
|
||||||
if(!child) {
|
if(!child) {
|
||||||
this.paddings.up = 0;
|
this.paddings.up = 0;
|
||||||
this.paddingTopDiv.style.height = '0px';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.prepend(child.element);
|
fragment.prepend(child.element);
|
||||||
let height = child.height;
|
|
||||||
|
needHeight -= child.height;
|
||||||
needHeight -= height;
|
this.paddings.up -= child.height;
|
||||||
this.paddings.up -= height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await fastdom.mutate(() => {
|
await fastdom.mutate(() => {
|
||||||
this.splitUp.prepend(fragment);
|
this.splitUp.prepend(fragment);
|
||||||
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
||||||
@ -378,30 +418,29 @@ export default class Scrollable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onBottomIntersection(entry: IntersectionObserverEntry) {
|
public async onBottomIntersection(/* entry: IntersectionObserverEntry */needHeight: number) {
|
||||||
console.log('onBottomIntersection');
|
console.log('onBottomIntersection', needHeight, this);
|
||||||
|
|
||||||
if(this.hiddenElements.down.length && this.paddings.down) {
|
if(this.hiddenElements.down.length && this.paddings.down) {
|
||||||
let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
//let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
||||||
|
//let needHeight = entry.intersectionRect.height || await fastdom.measure(() => this.splitUp.firstElementChild.scrollHeight);
|
||||||
|
|
||||||
let fragment = document.createDocumentFragment();
|
let fragment = document.createDocumentFragment();
|
||||||
while(needHeight > 0 && this.paddings.down) {
|
while(needHeight > 0 && this.paddings.down) {
|
||||||
let child = this.hiddenElements.down.shift();
|
let child = this.hiddenElements.down.shift();
|
||||||
|
|
||||||
if(!child) {
|
if(!child) {
|
||||||
this.paddings.down = 0;
|
this.paddings.down = 0;
|
||||||
this.paddingBottomDiv.style.height = '0px';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.appendChild(child.element);
|
fragment.appendChild(child.element);
|
||||||
let height = child.height;
|
|
||||||
|
needHeight -= child.height;
|
||||||
needHeight -= height;
|
this.paddings.down -= child.height;
|
||||||
this.paddings.down -= height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await fastdom.mutate(() => {
|
await fastdom.mutate(() => {
|
||||||
this.splitUp.appendChild(fragment);
|
this.splitUp.appendChild(fragment);
|
||||||
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
||||||
@ -413,15 +452,22 @@ export default class Scrollable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onScrolledBottom() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public prepend(...smth: (string | Node)[]) {
|
public prepend(...smth: (string | Node)[]) {
|
||||||
if(this.splitUp) {
|
if(this.splitUp) {
|
||||||
this.splitUp.prepend(...smth);
|
if(this.hiddenElements.up.length) {
|
||||||
|
smth.forEach(node => {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.hiddenElements.up.push({
|
||||||
|
element: node as Element,
|
||||||
|
height: (node as Element).scrollHeight || 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.splitUp.prepend(...smth);
|
||||||
|
}
|
||||||
|
|
||||||
for(let node of smth) {
|
for(let node of smth) {
|
||||||
if(typeof(node) !== 'string') {
|
if(typeof(node) !== 'string') {
|
||||||
this.splitObserver.unobserve(node as Element);
|
this.splitObserver.unobserve(node as Element);
|
||||||
@ -432,11 +478,22 @@ export default class Scrollable {
|
|||||||
this.container.prepend(...smth);
|
this.container.prepend(...smth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public append(...smth: (string | Node)[]) {
|
public append(...smth: (string | Node)[]) {
|
||||||
if(this.splitUp) {
|
if(this.splitUp) {
|
||||||
this.splitUp.append(...smth);
|
if(this.hiddenElements.down.length) {
|
||||||
|
smth.forEachReverse(node => {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.hiddenElements.down.unshift({
|
||||||
|
element: node as Element,
|
||||||
|
height: (node as Element).scrollHeight || 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.splitUp.append(...smth);
|
||||||
|
}
|
||||||
|
|
||||||
for(let node of smth) {
|
for(let node of smth) {
|
||||||
if(typeof(node) !== 'string') {
|
if(typeof(node) !== 'string') {
|
||||||
this.splitObserver.unobserve(node as Element);
|
this.splitObserver.unobserve(node as Element);
|
||||||
@ -447,32 +504,50 @@ export default class Scrollable {
|
|||||||
this.container.append(...smth);
|
this.container.append(...smth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public insertBefore(newChild: Element, refChild: Element) {
|
public insertBefore(newChild: Element, refChild: Element) {
|
||||||
if(this.splitUp) {
|
if(this.splitUp) {
|
||||||
this.splitObserver.unobserve(newChild);
|
this.splitObserver.unobserve(newChild);
|
||||||
|
this.splitObserver.observe(newChild);
|
||||||
|
|
||||||
|
let index = -1;
|
||||||
|
index = this.hiddenElements.up.findIndex(c => c.element == refChild);
|
||||||
|
|
||||||
|
// возможно здесь нужно очищать предыдущую высоту если newChild уже скрыт (но может и не нужно)
|
||||||
|
if(index !== -1) {
|
||||||
|
this.hiddenElements.up.splice(index, 0, {element: newChild, height: newChild.scrollHeight || 1});
|
||||||
|
return index;
|
||||||
|
} else {
|
||||||
|
index = this.hiddenElements.down.findIndex(c => c.element == newChild);
|
||||||
|
|
||||||
|
if(index !== -1) {
|
||||||
|
this.hiddenElements.down.splice(index, 0, {element: newChild, height: newChild.scrollHeight || 1});
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.splitUp.insertBefore(newChild, refChild);
|
return this.splitUp.insertBefore(newChild, refChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.container.insertBefore(newChild, refChild);
|
return this.container.insertBefore(newChild, refChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
set scrollTop(y: number) {
|
set scrollTop(y: number) {
|
||||||
this.container.scrollTop = y;
|
this.container.scrollTop = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
get scrollTop() {
|
get scrollTop() {
|
||||||
return this.container.scrollTop;
|
return this.container.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
get scrollHeight() {
|
get scrollHeight() {
|
||||||
return this.container.scrollHeight;
|
return this.container.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
get parentElement() {
|
get parentElement() {
|
||||||
return this.container.parentElement;
|
return this.container.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
get offsetHeight() {
|
get offsetHeight() {
|
||||||
return this.container.offsetHeight;
|
return this.container.offsetHeight;
|
||||||
}
|
}
|
||||||
|
535
src/components/scrollable_goodnew.ts
Normal file
535
src/components/scrollable_goodnew.ts
Normal file
@ -0,0 +1,535 @@
|
|||||||
|
import { cancelEvent } from "../lib/utils";
|
||||||
|
|
||||||
|
//import {measure} from 'fastdom/fastdom.min';
|
||||||
|
import FastDom from 'fastdom';
|
||||||
|
import 'fastdom/src/fastdom-strict'; // exclude in production
|
||||||
|
import FastDomPromised from 'fastdom/extensions/fastdom-promised';
|
||||||
|
|
||||||
|
//const fastdom = FastDom.extend(FastDomPromised);
|
||||||
|
const fastdom = ((window as any).fastdom as typeof FastDom).extend(FastDomPromised);
|
||||||
|
|
||||||
|
(window as any).fastdom.strict(false);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
//(window as any).fastdom.strict(true);
|
||||||
|
}, 5e3);
|
||||||
|
|
||||||
|
|
||||||
|
export default class Scrollable {
|
||||||
|
public container: HTMLDivElement;
|
||||||
|
public thumb: HTMLDivElement;
|
||||||
|
|
||||||
|
public type: string;
|
||||||
|
public side: string;
|
||||||
|
public scrollType: string;
|
||||||
|
public scrollSide: string;
|
||||||
|
public clientAxis: string;
|
||||||
|
|
||||||
|
public scrollSize = -1;
|
||||||
|
public size = 0;
|
||||||
|
public thumbSize = 0;
|
||||||
|
|
||||||
|
public hiddenElements: {
|
||||||
|
up: {element: Element, height: number}[],
|
||||||
|
down: {element: Element, height: number}[]
|
||||||
|
} = {
|
||||||
|
up: [],
|
||||||
|
down: []
|
||||||
|
};
|
||||||
|
public paddings = {up: 0, down: 0};
|
||||||
|
|
||||||
|
public paddingTopDiv: HTMLDivElement;
|
||||||
|
public paddingBottomDiv: HTMLDivElement;
|
||||||
|
|
||||||
|
public splitUp: HTMLElement;
|
||||||
|
|
||||||
|
public onAddedBottom: () => void = null;
|
||||||
|
public onScrolledTop: () => void = null;
|
||||||
|
public onScrolledBottom: () => void = null;
|
||||||
|
|
||||||
|
public topObserver: IntersectionObserver;
|
||||||
|
public bottomObserver: IntersectionObserver;
|
||||||
|
|
||||||
|
public splitObserver: IntersectionObserver;
|
||||||
|
public splitMeasure: Promise<{element: Element, height: number}[]> = null;
|
||||||
|
public splitMutate: Promise</* void */number> = null;
|
||||||
|
|
||||||
|
constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300) {
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.classList.add('scrollable');
|
||||||
|
|
||||||
|
//let arr = [];
|
||||||
|
//for(let i = 0.001; i < 1; i += 0.001) arr.push(i);
|
||||||
|
this.topObserver = new IntersectionObserver(entries => {
|
||||||
|
let entry = entries[0];
|
||||||
|
|
||||||
|
console.log('top intersection:', entries, entry.isIntersecting, entry.intersectionRatio > 0);
|
||||||
|
if(entry.isIntersecting) {
|
||||||
|
//this.onTopIntersection(entry);
|
||||||
|
this.onTopIntersection(entry.intersectionRect.height);
|
||||||
|
|
||||||
|
if(this.onScrolledTop) this.onScrolledTop();
|
||||||
|
}
|
||||||
|
// console.log('top intersection end');
|
||||||
|
}, {/* threshold: arr, */root: this.el});
|
||||||
|
|
||||||
|
this.bottomObserver = new IntersectionObserver(entries => {
|
||||||
|
let entry = entries[0];
|
||||||
|
|
||||||
|
console.log('bottom intersection:', entries, entry.isIntersecting, entry.intersectionRatio > 0);
|
||||||
|
if(entry.isIntersecting) {
|
||||||
|
//this.onBottomIntersection(entry);
|
||||||
|
this.onBottomIntersection(entry.intersectionRect.height);
|
||||||
|
|
||||||
|
if(this.onScrolledBottom) this.onScrolledBottom();
|
||||||
|
}
|
||||||
|
}, {/* threshold: arr, */root: this.el});
|
||||||
|
|
||||||
|
if(x) {
|
||||||
|
this.container.classList.add('scrollable-x');
|
||||||
|
this.type = 'width';
|
||||||
|
this.side = 'left';
|
||||||
|
this.scrollType = 'scrollWidth';
|
||||||
|
this.scrollSide = 'scrollLeft';
|
||||||
|
this.clientAxis = 'clientX';
|
||||||
|
|
||||||
|
let scrollHorizontally = (e: any) => {
|
||||||
|
e = window.event || e;
|
||||||
|
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
|
||||||
|
this.container.scrollLeft -= (delta * 20);
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
if(this.container.addEventListener) {
|
||||||
|
// IE9, Chrome, Safari, Opera
|
||||||
|
this.container.addEventListener("mousewheel", scrollHorizontally, false);
|
||||||
|
// Firefox
|
||||||
|
this.container.addEventListener("DOMMouseScroll", scrollHorizontally, false);
|
||||||
|
} else {
|
||||||
|
// IE 6/7/8
|
||||||
|
// @ts-ignore
|
||||||
|
this.container.attachEvent("onmousewheel", scrollHorizontally);
|
||||||
|
}
|
||||||
|
} else if(y) {
|
||||||
|
this.container.classList.add('scrollable-y');
|
||||||
|
this.type = 'height';
|
||||||
|
this.side = 'top';
|
||||||
|
this.scrollType = 'scrollHeight';
|
||||||
|
this.scrollSide = 'scrollTop';
|
||||||
|
this.clientAxis = 'clientY';
|
||||||
|
} else {
|
||||||
|
throw new Error('no side for scroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.thumb = document.createElement('div');
|
||||||
|
this.thumb.className = 'scrollbar-thumb';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.thumb.style[this.type] = '30px';
|
||||||
|
|
||||||
|
// mouse scroll
|
||||||
|
let onMouseMove = (e: MouseEvent) => {
|
||||||
|
let rect = this.thumb.getBoundingClientRect();
|
||||||
|
|
||||||
|
let diff: number;
|
||||||
|
// @ts-ignore
|
||||||
|
diff = e[this.clientAxis] - rect[this.side];
|
||||||
|
// @ts-ignore
|
||||||
|
this.container[this.scrollSide] += diff * 0.5;
|
||||||
|
|
||||||
|
// console.log('onMouseMove', e, diff);
|
||||||
|
|
||||||
|
cancelEvent(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.thumb.addEventListener('mousedown', () => {
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', () => {
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
}, {once: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg
|
||||||
|
window.addEventListener('resize', this.resize.bind(this));
|
||||||
|
|
||||||
|
this.paddingTopDiv = document.createElement('div');
|
||||||
|
this.paddingTopDiv.classList.add('scroll-padding');
|
||||||
|
this.paddingBottomDiv = document.createElement('div');
|
||||||
|
this.paddingBottomDiv.classList.add('scroll-padding');
|
||||||
|
|
||||||
|
this.topObserver.observe(this.paddingTopDiv);
|
||||||
|
this.bottomObserver.observe(this.paddingBottomDiv);
|
||||||
|
|
||||||
|
this.container.addEventListener('scroll', this.onScroll.bind(this));
|
||||||
|
|
||||||
|
Array.from(el.children).forEach(c => this.container.append(c));
|
||||||
|
|
||||||
|
el.append(this.container);
|
||||||
|
this.container.parentElement.append(this.thumb);
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public splitObserve(entries: IntersectionObserverEntry[]) {
|
||||||
|
console.log('splitObserver', entries);
|
||||||
|
for(let entry of entries) { // there may be duplicates (1st - not intersecting, 2nd - intersecting)
|
||||||
|
//console.log('onscroll entry', entry.target, entry.isIntersecting, entry);
|
||||||
|
if(!entry.isIntersecting && entry.target.parentElement && entry.rootBounds) {
|
||||||
|
let child = entry.target;
|
||||||
|
|
||||||
|
let isTop = entry.boundingClientRect.top <= 0;
|
||||||
|
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
|
||||||
|
console.log('onscroll entry', isTop, isBottom, child, entry);
|
||||||
|
|
||||||
|
let needHeight = this.splitOffset;
|
||||||
|
//console.log('will call measure');
|
||||||
|
if(isTop) { // when scrolling down
|
||||||
|
//this.onBottomIntersection(entry);
|
||||||
|
|
||||||
|
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
||||||
|
this.splitMeasure = fastdom.measure(() => {
|
||||||
|
let sliced: {element: Element, height: number}[] = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(needHeight > 0) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
} else {
|
||||||
|
sliced.push({element: child, height: child.scrollHeight});
|
||||||
|
}
|
||||||
|
} while(child = child.previousElementSibling);
|
||||||
|
return sliced;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasure.then(sliced => {
|
||||||
|
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
||||||
|
|
||||||
|
this.splitMutate = fastdom.mutate(() => {
|
||||||
|
let sum = 0;
|
||||||
|
sliced.forEachReverse((child) => {
|
||||||
|
let {element, height} = child;
|
||||||
|
if(!this.splitUp.contains(element)) return;
|
||||||
|
|
||||||
|
sum += height;
|
||||||
|
this.paddings.up += height;
|
||||||
|
this.hiddenElements.up.push(child);
|
||||||
|
this.splitUp.removeChild(element);
|
||||||
|
//element.parentElement.removeChild(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
||||||
|
return sum;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMutate.then(sum => {
|
||||||
|
this.onBottomIntersection(sum);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('onscroll sliced up', sliced);
|
||||||
|
} else if(isBottom) { // when scrolling top
|
||||||
|
//this.onTopIntersection(entry);
|
||||||
|
|
||||||
|
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
|
||||||
|
this.splitMeasure = fastdom.measure(() => {
|
||||||
|
let sliced: {element: Element, height: number}[] = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(needHeight > 0) {
|
||||||
|
needHeight -= child.scrollHeight;
|
||||||
|
} else {
|
||||||
|
sliced.push({element: child, height: child.scrollHeight});
|
||||||
|
}
|
||||||
|
} while(child = child.nextElementSibling);
|
||||||
|
return sliced;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMeasure.then(sliced => {
|
||||||
|
if(this.splitMutate) fastdom.clear(this.splitMutate);
|
||||||
|
|
||||||
|
this.splitMutate = fastdom.mutate(() => {
|
||||||
|
let sum = 0;
|
||||||
|
sliced.forEachReverse((child) => {
|
||||||
|
let {element, height} = child;
|
||||||
|
if(!this.splitUp.contains(element)) return;
|
||||||
|
|
||||||
|
sum += height;
|
||||||
|
this.paddings.down += height;
|
||||||
|
this.hiddenElements.down.unshift(child);
|
||||||
|
this.splitUp.removeChild(element);
|
||||||
|
//element.parentElement.removeChild(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
||||||
|
return sum;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.splitMutate.then(sum => {
|
||||||
|
this.onTopIntersection(sum);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('onscroll sliced down', sliced);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('splitObserver', entry, entry.target, isTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resize() {
|
||||||
|
//console.time('scroll resize');
|
||||||
|
|
||||||
|
await fastdom.measure(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
this.scrollSize = this.container[this.scrollType];
|
||||||
|
|
||||||
|
let rect = this.container.getBoundingClientRect();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.size = rect[this.type];
|
||||||
|
});
|
||||||
|
|
||||||
|
await fastdom.mutate(() => {
|
||||||
|
if(!this.size || this.size == this.scrollSize) {
|
||||||
|
this.thumbSize = 0;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.thumb.style[this.type] = this.thumbSize + 'px';
|
||||||
|
//console.timeEnd('scroll resize');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//if(!height) return;
|
||||||
|
|
||||||
|
let divider = this.scrollSize / this.size / 0.5;
|
||||||
|
this.thumbSize = this.size / divider;
|
||||||
|
|
||||||
|
if(this.thumbSize < 20) this.thumbSize = 20;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.thumb.style[this.type] = this.thumbSize + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.timeEnd('scroll resize');
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
//console.log('onresize', thumb.style[type], thumbHeight, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setVirtualContainer(el?: HTMLElement) {
|
||||||
|
this.splitUp = el;
|
||||||
|
|
||||||
|
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
|
||||||
|
this.paddings.up = this.paddings.down = 0;
|
||||||
|
|
||||||
|
if(this.paddingTopDiv.parentElement) {
|
||||||
|
fastdom.mutate(() => {
|
||||||
|
this.paddingTopDiv.style.height = '';
|
||||||
|
this.paddingBottomDiv.style.height = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.splitObserver) {
|
||||||
|
this.splitObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.splitObserver = new IntersectionObserver((entries) => this.splitObserve(entries), {root: this.el});
|
||||||
|
|
||||||
|
if(el) {
|
||||||
|
fastdom.mutate(() => {
|
||||||
|
el.parentElement.insertBefore(this.paddingTopDiv, el);
|
||||||
|
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onScroll() {
|
||||||
|
//console.time('scroll onScroll');
|
||||||
|
let {value, maxValue} = await fastdom.measure(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
|
||||||
|
let maxValue = 100 - (this.thumbSize / this.size * 100);
|
||||||
|
|
||||||
|
return {value, maxValue};
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
|
||||||
|
fastdom.mutate(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.timeEnd('scroll onScroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onTopIntersection(/* entry: IntersectionObserverEntry */needHeight: number) {
|
||||||
|
console.log('onTopIntersection', needHeight, this);
|
||||||
|
|
||||||
|
if(this.hiddenElements.up.length && this.paddings.up) {
|
||||||
|
//let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
||||||
|
//let needHeight = entry.intersectionRect.height || await fastdom.measure(() => this.splitUp.lastElementChild.scrollHeight);
|
||||||
|
|
||||||
|
let fragment = document.createDocumentFragment();
|
||||||
|
while(needHeight > 0 && this.paddings.up) {
|
||||||
|
let child = this.hiddenElements.up.pop();
|
||||||
|
|
||||||
|
// console.log('top returning from hidden', child);
|
||||||
|
|
||||||
|
if(!child) {
|
||||||
|
this.paddings.up = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.prepend(child.element);
|
||||||
|
|
||||||
|
needHeight -= child.height;
|
||||||
|
this.paddings.up -= child.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fastdom.mutate(() => {
|
||||||
|
this.splitUp.prepend(fragment);
|
||||||
|
this.paddingTopDiv.style.height = this.paddings.up + 'px';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fastdom.mutate(() => {
|
||||||
|
this.paddingTopDiv.style.height = '0px';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onBottomIntersection(/* entry: IntersectionObserverEntry */needHeight: number) {
|
||||||
|
console.log('onBottomIntersection', needHeight, this);
|
||||||
|
|
||||||
|
if(this.hiddenElements.down.length && this.paddings.down) {
|
||||||
|
//let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
|
||||||
|
//let needHeight = entry.intersectionRect.height || await fastdom.measure(() => this.splitUp.firstElementChild.scrollHeight);
|
||||||
|
|
||||||
|
let fragment = document.createDocumentFragment();
|
||||||
|
while(needHeight > 0 && this.paddings.down) {
|
||||||
|
let child = this.hiddenElements.down.shift();
|
||||||
|
|
||||||
|
if(!child) {
|
||||||
|
this.paddings.down = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.appendChild(child.element);
|
||||||
|
|
||||||
|
needHeight -= child.height;
|
||||||
|
this.paddings.down -= child.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fastdom.mutate(() => {
|
||||||
|
this.splitUp.appendChild(fragment);
|
||||||
|
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
|
||||||
|
});
|
||||||
|
if(this.onAddedBottom) this.onAddedBottom();
|
||||||
|
} else {
|
||||||
|
await fastdom.mutate(() => {
|
||||||
|
this.paddingBottomDiv.style.height = '0px';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepend(...smth: (string | Node)[]) {
|
||||||
|
if(this.splitUp) {
|
||||||
|
if(this.hiddenElements.up.length) {
|
||||||
|
smth.forEach(node => {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.hiddenElements.up.push({
|
||||||
|
element: node as Element,
|
||||||
|
height: (node as Element).scrollHeight || 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.splitUp.prepend(...smth);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let node of smth) {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.splitObserver.unobserve(node as Element);
|
||||||
|
this.splitObserver.observe(node as Element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.container.prepend(...smth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public append(...smth: (string | Node)[]) {
|
||||||
|
if(this.splitUp) {
|
||||||
|
if(this.hiddenElements.down.length) {
|
||||||
|
smth.forEachReverse(node => {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.hiddenElements.down.unshift({
|
||||||
|
element: node as Element,
|
||||||
|
height: (node as Element).scrollHeight || 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.splitUp.append(...smth);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let node of smth) {
|
||||||
|
if(typeof(node) !== 'string') {
|
||||||
|
this.splitObserver.unobserve(node as Element);
|
||||||
|
this.splitObserver.observe(node as Element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.container.append(...smth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public insertBefore(newChild: Element, refChild: Element) {
|
||||||
|
if(this.splitUp) {
|
||||||
|
this.splitObserver.unobserve(newChild);
|
||||||
|
this.splitObserver.observe(newChild);
|
||||||
|
|
||||||
|
let index = -1;
|
||||||
|
index = this.hiddenElements.up.findIndex(c => c.element == refChild);
|
||||||
|
|
||||||
|
// возможно здесь нужно очищать предыдущую высоту если newChild уже скрыт (но может и не нужно)
|
||||||
|
if(index !== -1) {
|
||||||
|
this.hiddenElements.up.splice(index, 0, {element: newChild, height: newChild.scrollHeight || 1});
|
||||||
|
return index;
|
||||||
|
} else {
|
||||||
|
index = this.hiddenElements.down.findIndex(c => c.element == newChild);
|
||||||
|
|
||||||
|
if(index !== -1) {
|
||||||
|
this.hiddenElements.down.splice(index, 0, {element: newChild, height: newChild.scrollHeight || 1});
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.splitUp.insertBefore(newChild, refChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.container.insertBefore(newChild, refChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
set scrollTop(y: number) {
|
||||||
|
this.container.scrollTop = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
get scrollTop() {
|
||||||
|
return this.container.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
get scrollHeight() {
|
||||||
|
return this.container.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parentElement() {
|
||||||
|
return this.container.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
get offsetHeight() {
|
||||||
|
return this.container.offsetHeight;
|
||||||
|
}
|
||||||
|
}
|
@ -256,10 +256,9 @@ export class AppImManager {
|
|||||||
this.pinnedMessageContent.innerHTML = RichTextProcessor.wrapEmojiText(message.message);
|
this.pinnedMessageContent.innerHTML = RichTextProcessor.wrapEmojiText(message.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = this.needUpdate.length;
|
this.needUpdate.forEachReverse((obj, idx) => {
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
if(obj.replyMid == mid) {
|
||||||
if(this.needUpdate[i].replyMid == mid) {
|
let {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
|
||||||
let {mid, replyMid} = this.needUpdate.splice(i, 1)[0];
|
|
||||||
|
|
||||||
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
|
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
|
||||||
let bubble = this.bubbles[mid];
|
let bubble = this.bubbles[mid];
|
||||||
@ -274,7 +273,7 @@ export class AppImManager {
|
|||||||
|
|
||||||
this.renderMessage(message, false, false, bubble, false);
|
this.renderMessage(message, false, false, bubble, false);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,19 +314,6 @@ export class AppImManager {
|
|||||||
|
|
||||||
if(['IMG', 'VIDEO', 'SVG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
|
if(['IMG', 'VIDEO', 'SVG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
|
||||||
|
|
||||||
/* if(target.tagName == 'VIDEO' && bubble.classList.contains('round')) {
|
|
||||||
let video = target as HTMLVideoElement;
|
|
||||||
video.currentTime = 0;
|
|
||||||
if(video.paused) {
|
|
||||||
video.play();
|
|
||||||
video.volume = 1;
|
|
||||||
} else {
|
|
||||||
video.pause();
|
|
||||||
video.volume = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} */
|
|
||||||
|
|
||||||
if(target.tagName == 'DIV') {
|
if(target.tagName == 'DIV') {
|
||||||
if(target.classList.contains('forward')) {
|
if(target.classList.contains('forward')) {
|
||||||
let savedFrom = bubble.dataset.savedFrom;
|
let savedFrom = bubble.dataset.savedFrom;
|
||||||
@ -720,24 +706,20 @@ export class AppImManager {
|
|||||||
if(!this.myID) return Promise.resolve();
|
if(!this.myID) return Promise.resolve();
|
||||||
|
|
||||||
appUsersManager.setUserStatus(this.myID, this.offline);
|
appUsersManager.setUserStatus(this.myID, this.offline);
|
||||||
return apiManager.invokeApi('account.updateStatus', {
|
return apiManager.invokeApi('account.updateStatus', {offline: this.offline});
|
||||||
offline: this.offline
|
|
||||||
}, {noErrorBox: true});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onScroll() {
|
public onScroll() {
|
||||||
let length = this.unreaded.length;
|
|
||||||
let readed: number[] = [];
|
let readed: number[] = [];
|
||||||
|
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
this.unreaded.forEachReverse((msgID, idx) => {
|
||||||
let msgID = this.unreaded[i];
|
|
||||||
let bubble = this.bubbles[msgID];
|
let bubble = this.bubbles[msgID];
|
||||||
|
|
||||||
if(isElementInViewport(bubble)) {
|
if(isElementInViewport(bubble)) {
|
||||||
readed.push(msgID);
|
readed.push(msgID);
|
||||||
this.unreaded.splice(i, 1);
|
this.unreaded.splice(idx, 1);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
lottieLoader.checkAnimations();
|
lottieLoader.checkAnimations();
|
||||||
|
|
||||||
@ -844,7 +826,7 @@ export class AppImManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setScroll() {
|
public setScroll() {
|
||||||
this.scrollable = new Scrollable(this.bubblesContainer, false, true, 1500);
|
this.scrollable = new Scrollable(this.bubblesContainer, false, true, 750/* 1500 */);
|
||||||
this.scroll = this.scrollable.container;
|
this.scroll = this.scrollable.container;
|
||||||
|
|
||||||
this.scrollable.setVirtualContainer(this.chatInner);
|
this.scrollable.setVirtualContainer(this.chatInner);
|
||||||
@ -1056,7 +1038,7 @@ export class AppImManager {
|
|||||||
this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title;
|
this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title;
|
||||||
|
|
||||||
this.topbar.style.display = this.goDownBtn.style.display = '';
|
this.topbar.style.display = this.goDownBtn.style.display = '';
|
||||||
appSidebarRight.toggleSidebar(true);
|
//appSidebarRight.toggleSidebar(true);
|
||||||
|
|
||||||
this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : '';
|
this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : '';
|
||||||
|
|
||||||
@ -1142,15 +1124,14 @@ export class AppImManager {
|
|||||||
///////this.log('updateUnreadByDialog', maxID, dialog, this.unreadOut);
|
///////this.log('updateUnreadByDialog', maxID, dialog, this.unreadOut);
|
||||||
|
|
||||||
let length = this.unreadOut.length;
|
let length = this.unreadOut.length;
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
this.unreadOut.forEachReverse((msgID, idx) => {
|
||||||
let msgID = this.unreadOut[i];
|
|
||||||
if(msgID > 0 && msgID <= maxID) {
|
if(msgID > 0 && msgID <= maxID) {
|
||||||
let bubble = this.bubbles[msgID];
|
let bubble = this.bubbles[msgID];
|
||||||
bubble.classList.remove('is-sent');
|
bubble.classList.remove('is-sent');
|
||||||
bubble.classList.add('is-read');
|
bubble.classList.add('is-read');
|
||||||
this.unreadOut.splice(i, 1);
|
this.unreadOut.splice(idx, 1);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteMessagesByIDs(msgIDs: number[]) {
|
public deleteMessagesByIDs(msgIDs: number[]) {
|
||||||
@ -1632,11 +1613,6 @@ export class AppImManager {
|
|||||||
|
|
||||||
if(updatePosition) {
|
if(updatePosition) {
|
||||||
bubble.classList.add(our ? 'is-out' : 'is-in');
|
bubble.classList.add(our ? 'is-out' : 'is-in');
|
||||||
/* if(reverse) {
|
|
||||||
this.chatInner.prepend(bubble);
|
|
||||||
} else {
|
|
||||||
this.chatInner.append(bubble);
|
|
||||||
} */
|
|
||||||
if(reverse) {
|
if(reverse) {
|
||||||
this.scrollable.prepend(bubble);
|
this.scrollable.prepend(bubble);
|
||||||
} else {
|
} else {
|
||||||
@ -1673,14 +1649,11 @@ export class AppImManager {
|
|||||||
firstTimestamp: date.getTime()
|
firstTimestamp: date.getTime()
|
||||||
};
|
};
|
||||||
|
|
||||||
//this.chatInner.insertBefore(div, containerDiv);
|
this.scrollable.insertBefore(div, bubble);
|
||||||
//containerDiv.insertBefore(div, bubble);
|
|
||||||
this.scrollable.insertBefore(div, bubble);// this.chatInner.insertBefore(div, bubble);
|
|
||||||
} else {
|
} else {
|
||||||
let dateMessage = this.dateMessages[dateTimestamp];
|
let dateMessage = this.dateMessages[dateTimestamp];
|
||||||
if(dateMessage.firstTimestamp > date.getTime()) {
|
if(dateMessage.firstTimestamp > date.getTime()) {
|
||||||
//this.chatInner.insertBefore(dateMessage.div, containerDiv);
|
this.scrollable.insertBefore(dateMessage.div, bubble);
|
||||||
this.scrollable.insertBefore(dateMessage.div, bubble);// this.chatInner.insertBefore(dateMessage.div, bubble);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1765,14 +1738,11 @@ export class AppImManager {
|
|||||||
this.scrollPosition.prepareFor(reverse ? 'up' : 'down');
|
this.scrollPosition.prepareFor(reverse ? 'up' : 'down');
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = history.length;
|
history.forEachReverse((msgID: number) => {
|
||||||
for(let i = length - 1; i >= 0; --i) {
|
|
||||||
let msgID = history[i];
|
|
||||||
|
|
||||||
let message = appMessagesManager.getMessage(msgID);
|
let message = appMessagesManager.getMessage(msgID);
|
||||||
|
|
||||||
this.renderMessage(message, reverse, true);
|
this.renderMessage(message, reverse, true);
|
||||||
}
|
});
|
||||||
|
|
||||||
if(!isBackLimit) {
|
if(!isBackLimit) {
|
||||||
this.scrollPosition.restore();
|
this.scrollPosition.restore();
|
||||||
|
@ -46,20 +46,21 @@ Uint8Array.prototype.concat = function(...args: Array<Uint8Array | ArrayBuffer |
|
|||||||
return bufferConcats(this, ...args);
|
return bufferConcats(this, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Uint8Array.prototype.concat = function(array: number[] | ArrayBuffer | Uint8Array) {
|
Array.prototype.forEachReverse = function<T>(callback: (value: T, index?: number, array?: Array<T>) => void) {
|
||||||
let res = new Uint8Array(this.length + (array instanceof ArrayBuffer ? array.byteLength : array.length));
|
let length = this.length;
|
||||||
|
for(var i = length - 1; i >= 0; --i) {
|
||||||
res.set(this);
|
callback(this[i], i, this);
|
||||||
res.set(array instanceof ArrayBuffer ? new Uint8Array(array) : array, this.length);
|
}
|
||||||
|
};
|
||||||
return res;
|
|
||||||
}; */
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Uint8Array {
|
interface Uint8Array {
|
||||||
hex: string;
|
hex: string;
|
||||||
randomize: () => Uint8Array,
|
randomize: () => Uint8Array,
|
||||||
//concat: (array: number[] | ArrayBuffer | Uint8Array) => Uint8Array
|
|
||||||
concat: (...args: Array<Uint8Array | ArrayBuffer | number[]>) => Uint8Array
|
concat: (...args: Array<Uint8Array | ArrayBuffer | number[]>) => Uint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Array<T> {
|
||||||
|
forEachReverse(callback: (value: T, index?: number, array?: Array<T>) => void): void;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user