forked from EVOgeek/Vmeda.Online
MOBILE-3810 core: Customize page transition
parent
ccfad3b1ac
commit
a3b3277ea1
|
@ -21,7 +21,7 @@ import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
|
|
||||||
import { IonicModule, IonicRouteStrategy, iosTransitionAnimation } from '@ionic/angular';
|
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreModule } from '@/core/core.module';
|
import { CoreModule } from '@/core/core.module';
|
||||||
import { AddonsModule } from '@/addons/addons.module';
|
import { AddonsModule } from '@/addons/addons.module';
|
||||||
|
@ -31,6 +31,7 @@ import { AppRoutingModule } from './app-routing.module';
|
||||||
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
|
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
|
||||||
import { CoreCronDelegate } from '@services/cron';
|
import { CoreCronDelegate } from '@services/cron';
|
||||||
import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
|
import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
|
||||||
|
import { moodleTransitionAnimation } from '@classes/page-transition';
|
||||||
|
|
||||||
// For translate loader. AoT requires an exported function for factories.
|
// For translate loader. AoT requires an exported function for factories.
|
||||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||||
|
@ -44,7 +45,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
IonicModule.forRoot(
|
IonicModule.forRoot(
|
||||||
{
|
{
|
||||||
navAnimation: iosTransitionAnimation,
|
navAnimation: moodleTransitionAnimation,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
||||||
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { createAnimation } from '@ionic/angular';
|
||||||
|
import { Animation, NavOptions } from '@ionic/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adaptation from Ionic 5 iOs transition.
|
||||||
|
*
|
||||||
|
* https://github.com/ionic-team/ionic-framework/blob/5.8.x/core/src/utils/transition/ios.transition.ts
|
||||||
|
* Removed large title options, translucent header, buttons-collapse and header-collapse-condense-inactive.
|
||||||
|
* Changed large title check by collapsible header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DURATION = 540;
|
||||||
|
|
||||||
|
const getIonPageElement = (element: HTMLElement): HTMLElement | null => {
|
||||||
|
if (element.classList.contains('ion-page')) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ionPage: HTMLElement | null = element.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs');
|
||||||
|
if (ionPage) {
|
||||||
|
return ionPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// idk, return the original element so at least something animates and we don't have a null pointer
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface TransitionOptions extends NavOptions {
|
||||||
|
progressCallback?: ((ani: Animation | undefined) => void);
|
||||||
|
baseEl: HTMLElement;
|
||||||
|
enteringEl: HTMLElement;
|
||||||
|
leavingEl: HTMLElement | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadow = <T extends Element>(el: T): ShadowRoot | T => el.shadowRoot || el;
|
||||||
|
|
||||||
|
export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptions): Animation => {
|
||||||
|
const EASING = 'cubic-bezier(0.32,0.72,0,1)';
|
||||||
|
const OPACITY = 'opacity';
|
||||||
|
const TRANSFORM = 'transform';
|
||||||
|
const CENTER = '0%';
|
||||||
|
const OFF_OPACITY = 0.8;
|
||||||
|
|
||||||
|
const isRTL = navEl.ownerDocument.dir === 'rtl';
|
||||||
|
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
|
||||||
|
const OFF_LEFT = isRTL ? '33%' : '-33%';
|
||||||
|
|
||||||
|
const enteringEl = opts.enteringEl;
|
||||||
|
const leavingEl = opts.leavingEl;
|
||||||
|
|
||||||
|
const backDirection = (opts.direction === 'back');
|
||||||
|
const contentEl = enteringEl.querySelector(':scope > ion-content');
|
||||||
|
const headerEls = enteringEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
|
||||||
|
const enteringToolBarEls = enteringEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||||
|
|
||||||
|
const rootAnimation = createAnimation();
|
||||||
|
const enteringContentAnimation = createAnimation();
|
||||||
|
|
||||||
|
rootAnimation
|
||||||
|
.addElement(enteringEl)
|
||||||
|
.duration(opts.duration || DURATION)
|
||||||
|
.easing(opts.easing || EASING)
|
||||||
|
.fill('both')
|
||||||
|
.beforeRemoveClass('ion-page-invisible');
|
||||||
|
|
||||||
|
if (leavingEl && navEl) {
|
||||||
|
const navDecorAnimation = createAnimation();
|
||||||
|
navDecorAnimation.addElement(navEl);
|
||||||
|
rootAnimation.addAnimation(navDecorAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) {
|
||||||
|
enteringContentAnimation.addElement(
|
||||||
|
enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs') || [],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
enteringContentAnimation.addElement(contentEl || []);
|
||||||
|
enteringContentAnimation.addElement(headerEls);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootAnimation.addAnimation(enteringContentAnimation);
|
||||||
|
|
||||||
|
if (backDirection) {
|
||||||
|
enteringContentAnimation
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`)
|
||||||
|
.fromTo(OPACITY, OFF_OPACITY, 1);
|
||||||
|
} else {
|
||||||
|
// entering content, forward direction
|
||||||
|
enteringContentAnimation
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentEl) {
|
||||||
|
const enteringTransitionEffectEl: HTMLElement | null = shadow(contentEl).querySelector('.transition-effect');
|
||||||
|
if (enteringTransitionEffectEl) {
|
||||||
|
const enteringTransitionCoverEl: HTMLElement | null = enteringTransitionEffectEl.querySelector('.transition-cover');
|
||||||
|
const enteringTransitionShadowEl: HTMLElement | null = enteringTransitionEffectEl.querySelector('.transition-shadow');
|
||||||
|
|
||||||
|
if (!enteringTransitionCoverEl || !enteringTransitionShadowEl) {
|
||||||
|
return rootAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enteringTransitionEffect = createAnimation();
|
||||||
|
const enteringTransitionCover = createAnimation();
|
||||||
|
const enteringTransitionShadow = createAnimation();
|
||||||
|
|
||||||
|
enteringTransitionEffect
|
||||||
|
.addElement(enteringTransitionEffectEl)
|
||||||
|
.beforeStyles({ opacity: '1', display: 'block' })
|
||||||
|
.afterStyles({ opacity: '', display: '' });
|
||||||
|
|
||||||
|
enteringTransitionCover
|
||||||
|
.addElement(enteringTransitionCoverEl)
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(OPACITY, 0, 0.1);
|
||||||
|
|
||||||
|
enteringTransitionShadow
|
||||||
|
.addElement(enteringTransitionShadowEl)
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(OPACITY, 0.03, 0.70);
|
||||||
|
|
||||||
|
enteringTransitionEffect.addAnimation([enteringTransitionCover, enteringTransitionShadow]);
|
||||||
|
enteringContentAnimation.addAnimation([enteringTransitionEffect]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const enteringContentHasCollapsibleTitle = enteringEl.querySelector('ion-header[collapsible]');
|
||||||
|
enteringToolBarEls.forEach(enteringToolBarEl => {
|
||||||
|
const enteringToolBar = createAnimation();
|
||||||
|
enteringToolBar.addElement(enteringToolBarEl);
|
||||||
|
rootAnimation.addAnimation(enteringToolBar);
|
||||||
|
|
||||||
|
const enteringTitle = createAnimation();
|
||||||
|
enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title') || []);
|
||||||
|
|
||||||
|
const enteringToolBarButtons = createAnimation();
|
||||||
|
const buttons: HTMLIonButtonsElement[] = Array.from(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||||
|
|
||||||
|
enteringToolBarButtons.addElement(buttons);
|
||||||
|
|
||||||
|
const enteringToolBarItems = createAnimation();
|
||||||
|
enteringToolBarItems.addElement(
|
||||||
|
enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enteringToolBarBg = createAnimation();
|
||||||
|
enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background') || []);
|
||||||
|
|
||||||
|
const enteringBackButton = createAnimation();
|
||||||
|
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
|
||||||
|
|
||||||
|
if (backButtonEl) {
|
||||||
|
enteringBackButton.addElement(backButtonEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
enteringToolBar.addAnimation(
|
||||||
|
[enteringTitle, enteringToolBarButtons, enteringToolBarItems, enteringToolBarBg, enteringBackButton],
|
||||||
|
);
|
||||||
|
enteringToolBarButtons.fromTo(OPACITY, 0.01, 1);
|
||||||
|
enteringToolBarItems.fromTo(OPACITY, 0.01, 1);
|
||||||
|
|
||||||
|
if (backDirection) {
|
||||||
|
enteringTitle
|
||||||
|
.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`)
|
||||||
|
.fromTo(OPACITY, 0.01, 1);
|
||||||
|
|
||||||
|
enteringToolBarItems.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`);
|
||||||
|
|
||||||
|
// back direction, entering page has a back button
|
||||||
|
enteringBackButton.fromTo(OPACITY, 0.01, 1);
|
||||||
|
} else {
|
||||||
|
// entering toolbar, forward direction
|
||||||
|
if (!enteringContentHasCollapsibleTitle) {
|
||||||
|
enteringTitle
|
||||||
|
.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`)
|
||||||
|
.fromTo(OPACITY, 0.01, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
enteringToolBarItems.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
|
||||||
|
enteringToolBarBg.beforeClearStyles([OPACITY, 'transform']);
|
||||||
|
|
||||||
|
enteringToolBarBg.fromTo(OPACITY, 0.01, 'var(--opacity)');
|
||||||
|
|
||||||
|
// forward direction, entering page has a back button
|
||||||
|
enteringBackButton.fromTo(OPACITY, 0.01, 1);
|
||||||
|
|
||||||
|
if (backButtonEl) {
|
||||||
|
const enteringBackBtnText = createAnimation();
|
||||||
|
enteringBackBtnText
|
||||||
|
.addElement(shadow(backButtonEl).querySelector('.button-text') || [])
|
||||||
|
.fromTo('transform', (isRTL ? 'translateX(-100px)' : 'translateX(100px)'), 'translateX(0px)');
|
||||||
|
|
||||||
|
enteringToolBar.addAnimation(enteringBackBtnText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup leaving view
|
||||||
|
if (leavingEl) {
|
||||||
|
const leavingContent = createAnimation();
|
||||||
|
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
||||||
|
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||||
|
const leavingHeaderEls = leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
|
||||||
|
|
||||||
|
if (!leavingContentEl && leavingToolBarEls.length === 0 && leavingHeaderEls.length === 0) {
|
||||||
|
leavingContent.addElement(
|
||||||
|
leavingEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs') || [],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
leavingContent.addElement(leavingContentEl || []);
|
||||||
|
leavingContent.addElement(leavingHeaderEls);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootAnimation.addAnimation(leavingContent);
|
||||||
|
|
||||||
|
if (backDirection) {
|
||||||
|
// leaving content, back direction
|
||||||
|
leavingContent
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
|
||||||
|
|
||||||
|
const leavingPage = getIonPageElement(leavingEl) as HTMLElement;
|
||||||
|
rootAnimation.afterAddWrite(() => {
|
||||||
|
if (rootAnimation.getDirection() === 'normal') {
|
||||||
|
leavingPage.style.setProperty('display', 'none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// leaving content, forward direction
|
||||||
|
leavingContent
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
|
||||||
|
.fromTo(OPACITY, 1, OFF_OPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leavingContentEl) {
|
||||||
|
const leavingTransitionEffectEl = shadow(leavingContentEl).querySelector('.transition-effect');
|
||||||
|
|
||||||
|
if (leavingTransitionEffectEl) {
|
||||||
|
const leavingTransitionCoverEl = leavingTransitionEffectEl.querySelector('.transition-cover');
|
||||||
|
const leavingTransitionShadowEl = leavingTransitionEffectEl.querySelector('.transition-shadow');
|
||||||
|
|
||||||
|
const leavingTransitionEffect = createAnimation();
|
||||||
|
const leavingTransitionCover = createAnimation();
|
||||||
|
const leavingTransitionShadow = createAnimation();
|
||||||
|
|
||||||
|
leavingTransitionEffect
|
||||||
|
.addElement(leavingTransitionEffectEl)
|
||||||
|
.beforeStyles({ opacity: '1', display: 'block' })
|
||||||
|
.afterStyles({ opacity: '', display: '' });
|
||||||
|
|
||||||
|
leavingTransitionCover
|
||||||
|
.addElement(leavingTransitionCoverEl || [])
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(OPACITY, 0.1, 0);
|
||||||
|
|
||||||
|
leavingTransitionShadow
|
||||||
|
.addElement(leavingTransitionShadowEl || [])
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(OPACITY, 0.70, 0.03);
|
||||||
|
|
||||||
|
leavingTransitionEffect.addAnimation([leavingTransitionCover, leavingTransitionShadow]);
|
||||||
|
leavingContent.addAnimation([leavingTransitionEffect]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leavingToolBarEls.forEach(leavingToolBarEl => {
|
||||||
|
const leavingToolBar = createAnimation();
|
||||||
|
leavingToolBar.addElement(leavingToolBarEl);
|
||||||
|
|
||||||
|
const leavingTitle = createAnimation();
|
||||||
|
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title') || []);
|
||||||
|
|
||||||
|
const leavingToolBarButtons = createAnimation();
|
||||||
|
const buttons: HTMLIonButtonsElement[] = Array.from(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||||
|
|
||||||
|
leavingToolBarButtons.addElement(buttons);
|
||||||
|
|
||||||
|
const leavingToolBarItems = createAnimation();
|
||||||
|
const leavingToolBarItemEls =
|
||||||
|
leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
|
||||||
|
if (leavingToolBarItemEls.length > 0) {
|
||||||
|
leavingToolBarItems.addElement(leavingToolBarItemEls);
|
||||||
|
}
|
||||||
|
|
||||||
|
const leavingToolBarBg = createAnimation();
|
||||||
|
leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background') || []);
|
||||||
|
|
||||||
|
const leavingBackButton = createAnimation();
|
||||||
|
const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
|
||||||
|
if (backButtonEl) {
|
||||||
|
leavingBackButton.addElement(backButtonEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
leavingToolBar.addAnimation(
|
||||||
|
[leavingTitle, leavingToolBarButtons, leavingToolBarItems, leavingBackButton, leavingToolBarBg],
|
||||||
|
);
|
||||||
|
rootAnimation.addAnimation(leavingToolBar);
|
||||||
|
|
||||||
|
// fade out leaving toolbar items
|
||||||
|
leavingBackButton.fromTo(OPACITY, 0.99, 0);
|
||||||
|
|
||||||
|
leavingToolBarButtons.fromTo(OPACITY, 0.99, 0);
|
||||||
|
leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
|
||||||
|
|
||||||
|
if (backDirection) {
|
||||||
|
// leaving toolbar, back direction
|
||||||
|
leavingTitle
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'))
|
||||||
|
.fromTo(OPACITY, 0.99, 0);
|
||||||
|
|
||||||
|
leavingToolBarItems.fromTo(
|
||||||
|
'transform',
|
||||||
|
`translateX(${CENTER})`,
|
||||||
|
(isRTL ? 'translateX(-100%)' : 'translateX(100%)'),
|
||||||
|
);
|
||||||
|
leavingToolBarBg.beforeClearStyles([OPACITY, 'transform']);
|
||||||
|
// leaving toolbar, back direction, and there's no entering toolbar
|
||||||
|
// should just slide out, no fading out
|
||||||
|
leavingToolBarBg.fromTo(OPACITY, 'var(--opacity)', 0);
|
||||||
|
|
||||||
|
if (backButtonEl) {
|
||||||
|
const leavingBackBtnText = createAnimation();
|
||||||
|
leavingBackBtnText
|
||||||
|
.addElement(shadow(backButtonEl).querySelector('.button-text') || [])
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, `translateX(${(isRTL ? -124 : 124) + 'px'})`);
|
||||||
|
leavingToolBar.addAnimation(leavingBackBtnText);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// leaving toolbar, forward direction
|
||||||
|
leavingTitle
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
|
||||||
|
.fromTo(OPACITY, 0.99, 0)
|
||||||
|
.afterClearStyles([TRANSFORM, OPACITY]);
|
||||||
|
|
||||||
|
leavingToolBarItems
|
||||||
|
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
|
||||||
|
.afterClearStyles([TRANSFORM, OPACITY]);
|
||||||
|
|
||||||
|
leavingBackButton.afterClearStyles([OPACITY]);
|
||||||
|
leavingTitle.afterClearStyles([OPACITY]);
|
||||||
|
leavingToolBarButtons.afterClearStyles([OPACITY]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootAnimation;
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue