Merge pull request #3118 from NoelDeMartin/MOBILE-3982

MOBILE-3982 core: Fix collapsible header flicker
main
Pau Ferrer Ocaña 2022-02-16 14:15:34 +01:00 committed by GitHub
commit 2228b398fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 51 additions and 28 deletions

View File

@ -34,6 +34,7 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
protected loadingObserver: CoreEventObserver; protected loadingObserver: CoreEventObserver;
protected content?: HTMLIonContentElement | null; protected content?: HTMLIonContentElement | null;
protected contentScroll?: HTMLElement;
protected header: HTMLIonHeaderElement; protected header: HTMLIonHeaderElement;
protected titleTopDifference = 1; protected titleTopDifference = 1;
protected h1StartDifference = 0; protected h1StartDifference = 0;
@ -46,9 +47,11 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
protected title?: HTMLElement | null; protected title?: HTMLElement | null;
protected titleHeight = 0; protected titleHeight = 0;
protected contentH1?: HTMLElement | null; protected contentH1?: HTMLElement | null;
protected debouncedUpdateCollapseProgress: () => void;
constructor(el: ElementRef<HTMLIonHeaderElement>) { constructor(el: ElementRef<HTMLIonHeaderElement>) {
this.header = el.nativeElement; this.header = el.nativeElement;
this.debouncedUpdateCollapseProgress = CoreUtils.debounce(() => this.updateCollapseProgress(), 50);
this.loadingObserver = CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data) => { this.loadingObserver = CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data) => {
if (!data.loaded) { if (!data.loaded) {
@ -67,6 +70,21 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
}); });
} }
/**
* Set content element.
*
* @param content Content element.
*/
protected async setContent(content?: HTMLIonContentElement | null): Promise<void> {
this.content = content;
if (content) {
this.contentScroll = await content.getScrollElement();
} else {
delete this.contentScroll;
}
}
/** /**
* Gets the loading content id to wait for the loading to finish. * Gets the loading content id to wait for the loading to finish.
* *
@ -74,7 +92,7 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
*/ */
protected async getLoadingId(): Promise<string | undefined> { protected async getLoadingId(): Promise<string | undefined> {
if (!this.content) { if (!this.content) {
this.content = this.header.parentElement?.querySelector('ion-content:not(.disable-scroll-y)'); this.setContent(this.header.parentElement?.querySelector('ion-content:not(.disable-scroll-y)'));
if (!this.content) { if (!this.content) {
this.cannotCollapse(); this.cannotCollapse();
@ -101,7 +119,7 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
* Call this function when header is not collapsible. * Call this function when header is not collapsible.
*/ */
protected cannotCollapse(): void { protected cannotCollapse(): void {
this.content = undefined; this.setContent();
this.loadingObserver.off(); this.loadingObserver.off();
this.header.classList.add('core-header-collapsed'); this.header.classList.add('core-header-collapsed');
} }
@ -112,7 +130,6 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected async setupRealTitle(): Promise<void> { protected async setupRealTitle(): Promise<void> {
if (!this.content) { if (!this.content) {
this.cannotCollapse(); this.cannotCollapse();
@ -141,7 +158,6 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
this.title = title; this.title = title;
this.titleHeight = title.getBoundingClientRect().height; this.titleHeight = title.getBoundingClientRect().height;
this.titleTopDifference = this.contentH1.getBoundingClientRect().top - headerH1.getBoundingClientRect().top; this.titleTopDifference = this.contentH1.getBoundingClientRect().top - headerH1.getBoundingClientRect().top;
if (this.titleTopDifference <= 0) { if (this.titleTopDifference <= 0) {
@ -224,7 +240,7 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
*/ */
async setupContent(parentId?: string, retries = 5): Promise<void> { async setupContent(parentId?: string, retries = 5): Promise<void> {
if (parentId) { if (parentId) {
this.content = this.header.parentElement?.querySelector(`#${parentId} ion-content:not(.disable-scroll-y)`); this.setContent(this.header.parentElement?.querySelector(`#${parentId} ion-content:not(.disable-scroll-y)`));
this.inContent = false; this.inContent = false;
if (!this.content && retries > 0) { if (!this.content && retries > 0) {
await CoreUtils.nextTick(); await CoreUtils.nextTick();
@ -233,7 +249,7 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
return; return;
} }
this.onScroll(this.content?.scrollTop || 0); this.updateCollapseProgress();
} }
if (!this.title || !this.content) { if (!this.title || !this.content) {
@ -260,55 +276,62 @@ export class CoreCollapsibleHeaderDirective implements OnDestroy {
} }
this.content.scrollEvents = true; this.content.scrollEvents = true;
this.content.addEventListener('ionScroll', (e: CustomEvent<ScrollDetail>): void => { this.content.addEventListener('ionScroll', ({ target }: CustomEvent<ScrollDetail>): void => {
if (e.target == this.content) { if (target !== this.content) {
this.onScroll(e.detail.scrollTop); return;
} }
this.updateCollapseProgress();
this.debouncedUpdateCollapseProgress();
}); });
} }
/** /**
* On scroll function. * Update collapse progress according to the current scroll position.
*
* @param scrollTop Scroll top measure.
*/ */
protected onScroll( protected updateCollapseProgress(): void {
scrollTop: number, if (!this.contentScroll || !this.title || !this.contentH1) {
): void {
if (!this.title || !this.contentH1) {
return; return;
} }
const progress = CoreMath.clamp(scrollTop / this.titleTopDifference, 0, 1); const collapsibleHeaderHeight = this.title.shadowRoot?.children[0].clientHeight ?? this.title.clientHeight;
const collapsed = progress >= 1; const scrollableHeight = this.contentScroll.scrollHeight - this.contentScroll.clientHeight;
const collapsedHeight = collapsibleHeaderHeight - this.title.clientHeight;
const progress = CoreMath.clamp(
scrollableHeight + collapsedHeight <= 2 * collapsibleHeaderHeight
? this.contentScroll.scrollTop / (this.contentScroll.scrollHeight - this.contentScroll.clientHeight)
: this.contentScroll.scrollTop / collapsibleHeaderHeight,
0,
1,
);
const collapsed = progress === 1;
if (!this.inContent) { if (!this.inContent) {
this.title.style.transform = 'translateY(-' + scrollTop + 'px)'; this.title.style.transform = `translateY(-${this.titleTopDifference * progress}px)`;
const height = this.titleHeight - scrollTop; this.title.style.height = `${collapsibleHeaderHeight * (1 - progress)}px`;
this.title.style.height = (height > 0 ? height : 0) + 'px';
} }
// Check total collapse. // Check total collapse.
this.header.classList.toggle('core-header-collapsed', collapsed); this.header.classList.toggle('core-header-collapsed', collapsed);
this.title.classList.toggle('collapsible-title-collapsed', collapsed); this.title.classList.toggle('collapsible-title-collapsed', collapsed);
this.title.classList.toggle('collapsible-title-collapse-started', scrollTop > 0); this.title.classList.toggle('collapsible-title-collapse-started', progress > 0);
this.title.classList.toggle('collapsible-title-collapse-nowrap', progress > 0.5); this.title.classList.toggle('collapsible-title-collapse-nowrap', progress > 0.5);
this.title.style.setProperty('--collapse-opacity', (1 - progress) +''); this.title.style.setProperty('--collapse-opacity', `${1 - progress}`);
if (collapsed) { if (collapsed) {
this.contentH1.style.transform = 'translateX(-' + this.h1StartDifference + 'px)'; this.contentH1.style.transform = `translateX(-${this.h1StartDifference}px)`;
this.contentH1.style.setProperty('font-size', this.headerH1FontSize + 'px'); this.contentH1.style.setProperty('font-size', `${this.headerH1FontSize}px`);
return; return;
} }
// Zoom font-size out. // Zoom font-size out.
const newFontSize = this.contentH1FontSize - ((this.contentH1FontSize - this.headerH1FontSize) * progress); const newFontSize = this.contentH1FontSize - ((this.contentH1FontSize - this.headerH1FontSize) * progress);
this.contentH1.style.setProperty('font-size', newFontSize + 'px'); this.contentH1.style.setProperty('font-size', `${newFontSize}px`);
// Move. // Move.
const newStart = - this.h1StartDifference * progress; const newStart = -this.h1StartDifference * progress;
this.contentH1.style.transform = 'translateX(' + newStart + 'px)'; this.contentH1.style.transform = `translateX(${newStart}px)`;
} }
/** /**