I have an Ionic (v6) Angular (v15) project where I'd like to have custom page transitions that are split into 3 parts:
- The current page blurs
- The current page performs some infinite loading cycle (When I try to use blur or opacity here, the whole thing breaks) that will indicate to the user that the page is loading
- The current page fades out, and the next page fades in at the same time
1, and 2 are started right away (and 2 is delayed by 1's duration). 3 awaits a signal from the next page before it can start.
All pages that will employ this strategy will extend NavigableComponent
Here's the page transition animation:
import { AnimationController, Animation, AnimationBuilder } from '@ionic/angular';
export const PAGE_FADE_DURATION: number = 500;
export const pageTransitionAnimation: AnimationBuilder = (baseEl: HTMLElement, opts?: any): Animation => {
const animationCtrl = new AnimationController();
const leaveDuration: number = 500;
const loadingDuration: number = 500;
const blurLeaving = animationCtrl.create()
.addElement(opts.leavingEl)
.duration(leaveDuration)
.easing('ease')
.fromTo('filter', 'blur(0px)', 'blur(5px)')
const loadingEntering = animationCtrl.create()
.addElement(opts.leavingEl)
.delay(leaveDuration)
.duration(loadingDuration)
.easing('ease')
.iterations(Infinity)
.direction('alternate')
.fromTo('left', '0px', '0px')
const fadeLeaving = animationCtrl.create()
.addElement(opts.leavingEl)
.duration(PAGE_FADE_DURATION)
.easing('ease')
.fromTo('opacity', 1, 0)
const showEntering = animationCtrl.create()
.addElement(opts.enteringEl)
.duration(PAGE_FADE_DURATION)
.easing('ease')
.fromTo('opacity', 0, 1)
const animationUntilLoaded = animationCtrl.create().addAnimation([ blurLeaving, loadingEntering ]);
opts.enteringEl.__data = {
__ON_ENTER_ANIMATION: animationCtrl.create().addAnimation([ fadeLeaving, showEntering ]),
__ON_LEAVE_ANIMATION: animationUntilLoaded,
};
return animationUntilLoaded;
}
Here's the Navigable component:
import { ElementRef, inject } from "@angular/core";
import { PopoverController, Animation } from "@ionic/angular";
import { LoggableComponent } from "./loggable-component";
import { ReplaySubject } from 'rxjs';
import { PAGE_FADE_DURATION } from "../animations/page-transition.animation";
export abstract class NavigableComponent extends LoggableComponent {
setReady() {
this._onReadySubject.next();
}
onReady: () => Promise<void> = async () => {
return new Promise((resolve, reject) => {
this._onReadySubject.subscribe(() => {
resolve();
});
});
};
private _onReadySubject: ReplaySubject<void> = new ReplaySubject();
private _el: ElementRef = inject(ElementRef);
constructor(
title: string,
enabled: boolean,
popoverController: PopoverController,
) {
super(title, enabled, popoverController);
this._el.nativeElement.__COMPONENT = this;
}
async ionViewWillEnter() {
if (this._el.nativeElement.__data) {
const onEnterAnimation = this._el.nativeElement.__data.__ON_ENTER_ANIMATION as Animation | undefined;
const onLeaveAnimation = this._el.nativeElement.__data.__ON_LEAVE_ANIMATION as Animation | undefined;
await this.onReady();
if (onEnterAnimation) {
onEnterAnimation.play();
}
if (onLeaveAnimation) {
setTimeout(() => {
onLeaveAnimation.iterations(0);
onLeaveAnimation.stop();
onLeaveAnimation.destroy();
}, PAGE_FADE_DURATION);
}
}
}
}
And here's the code each Component derived from NavigableComponent will have:
async ngAfterViewInit() {
await < ... some promise once page is ready >;
this.setReady();
}
This works for any first transition after I first load the app, but fails afterwards. I'm assuming this is because the Animation.iterations(Infinity) screws up the Ionic page lifecycle. Does anyone know how I can accomplish this? Am I on the right track?
There seems to be very spotty documentation on animating page transitions.
Any help would be appreciated.