When we launch a modal in an Ionic application, an animation is used to provide a smooth transition as it appears on the screen. The default animation for iOS and Android causes the modal to slide up from the bottom – the effect is more pronounced on iOS, but the animation is similar on both platforms. This animation is perfectly suited to the platforms it runs on and most people will just stick with the default animation (there is no specific reason not to).
However, it is possible to supply your own custom transition animations if you wish. In this tutorial, we are going to look at how we can modify the default animations that Ionic supplies to create our own open and close animations for modals.
We will be creating some custom animations that look like this:
Before We Get Started
Last updated for Ionic 4, beta.13
This is an advanced tutorial that assumes you already have a decent working knowledge of the Ionic framework. If you require more introductory level content on Ionic I would recommend checking out my book or the Ionic tutorials on my website.
1. Supplying an Animation to a Modal
The way in which we typically create a modal looks something like this:
this.modalCtrl
.create({
component: ModalPage,
})
.then((modal) => {
modal.present();
});
However, there are many more options that we can supply when creating this modal which are outlined here. Those options include an option to specify an enterAnimation
and a leaveAnimation
like this:
this.modalCtrl
.create({
component: ModalPage,
enterAnimation: myEnterAnimation,
leaveAnimation: myLeaveAnimation,
})
.then((modal) => {
modal.present();
});
All we need to do is create those myEnterAnimation
and myLeaveAnimation
animations. That is what we will be focusing on in the next step.
2. Creating a Custom Transition Animation
Creating the transition animation isn’t exactly an easy task, fortunately, the Ionic team have already done most of the work for us. We can use the existing animations that they have created for the default transitions and build our solution on top of that.
You can find those animations here. Our solution is going to look slightly different to this, but most of the code will remain the same. We are going to create new files for our animations, and those files are going to export
the animation we want to use.
Create a new file at src/app/animations/enter.ts and add the following:
import { Animation } from '@ionic/core';
export function myEnterAnimation(
AnimationC: Animation,
baseEl: HTMLElement
): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
wrapperAnimation.addElement(baseEl.querySelector('.modal-wrapper'));
wrapperAnimation
.beforeStyles({ opacity: 1 })
.fromTo('translateY', '100%', '0%');
backdropAnimation.fromTo('opacity', 0.01, 0.4);
return Promise.resolve(
baseAnimation
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(400)
.beforeAddClass('show-modal')
.add(backdropAnimation)
.add(wrapperAnimation)
);
}
We have pretty much just copy and pasted the code from Ionic directly in here, except that we are now importing Animation
from @ionic/core
.
This code might look somewhat intimidating, but there are only a few things you need to know for basic animations. The most important part is the wrapperAnimation
:
wrapperAnimation
.beforeStyles({ opacity: 1 })
.fromTo('translateY', '100%', '0%');
The fromTo
method animates the styles on the modal from
one thing to
another thing. In this case, the translateY
is being animated from 100%
(i.e. off-screen, to the bottom) to 0%
(i.e. in the center of the screen). You can change the properties being animated here, and you can event animate multiple properties by chaining the fromTo
method:
wrapperAnimation
.beforeStyles({ opacity: 1 })
.fromTo('translateY', '100%', '0%')
.fromTo('somethingelse', 'beforevalue', 'aftervalue')
.fromTo('somethingelse', 'beforevalue', 'aftervalue');
You may also wish to modify the duration
to change the length of the animation of the easing function. More complex animations may require more modifications, but you should be able to do a lot with just this.
Let’s also set up the leave animation before continuing:
Create a new file at src/app/animations/leave.ts and add the following:
import { Animation } from '@ionic/core';
export function myLeaveAnimation(AnimationC: Animation, baseEl: HTMLElement): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
const wrapperEl = baseEl.querySelector('.modal-wrapper');
wrapperAnimation.addElement(wrapperEl);
const wrapperElRect = wrapperEl!.getBoundingClientRect();
wrapperAnimation.beforeStyles({ 'opacity': 1 })
.fromTo('translateY', '0%', `${window.innerHeight - wrapperElRect.top}px`);
backdropAnimation.fromTo('opacity', 0.4, 0.0);
return Promise.resolve(baseAnimation
.addElement(baseEl)
.easing('ease-out')
.duration(250)
.add(backdropAnimation)
.add(wrapperAnimation));
}
We could actually make use of these animations with our modal now and they would work. That’s a bit boring though because the animation is exactly the same as the default one.
Instead, we are going to create a transition animation that will slide the modal in from the left, and out through the right.
This is an easy modification to make because it is mostly the same as the original animation, just in a different direction.
Modify src/app/animations/enter.ts to reflect the following:
import { Animation } from '@ionic/core';
export function myEnterAnimation(
AnimationC: Animation,
baseEl: HTMLElement
): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
wrapperAnimation.addElement(baseEl.querySelector('.modal-wrapper'));
wrapperAnimation
.beforeStyles({ opacity: 1 })
.fromTo('translateX', '-100%', '0%');
backdropAnimation.fromTo('opacity', 0.01, 0.4);
return Promise.resolve(
baseAnimation
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(400)
.beforeAddClass('show-modal')
.add(backdropAnimation)
.add(wrapperAnimation)
);
}
Modify src/app/animations/leave.ts to reflect the following:
import { Animation } from '@ionic/core';
export function myLeaveAnimation(AnimationC: Animation, baseEl: HTMLElement): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
const wrapperEl = baseEl.querySelector('.modal-wrapper');
wrapperAnimation.addElement(wrapperEl);
const wrapperElRect = wrapperEl!.getBoundingClientRect();
wrapperAnimation.beforeStyles({ 'opacity': 1 })
.fromTo('translateX', '0%', `${window.innerWidth - wrapperElRect.left}px`);
backdropAnimation.fromTo('opacity', 0.4, 0.0);
return Promise.resolve(baseAnimation
.addElement(baseEl)
.easing('ease-out')
.duration(250)
.add(backdropAnimation)
.add(wrapperAnimation));
}
The original animation was modifying translateY
to push the modal up and down, but now we are using translateX
to push the modal left and right. It is the exact same concept, except we are animating over the horizontal plane rather than the vertical plane. Also, keep in mind that we have changed the to
value in the leave animation to use the innerWidth
and left
values instead of innerHeight
and top
.
3. Using a Custom Transition for a Modal
We have our custom animation, now we just need to use it. Fortunately, this is extremely straight-forward. Just import the animation into the page where you are creating the modal:
import { myEnterAnimation } from '../animations/enter';
import { myLeaveAnimation } from '../animations/leave';
and then supply those animations when creating the modal:
this.modalCtrl
.create({
component: ModalPage,
enterAnimation: myEnterAnimation,
leaveAnimation: myLeaveAnimation,
})
.then((modal) => {
modal.present();
});
4. Getting Fancier
The animation we created was pretty simple, we mostly kept the code exactly the same but we changed the direction. Now we are going to create something a little more custom:
Modify src/app/animations/enter.ts to reflect the following:
import { Animation } from '@ionic/core';
export function myEnterAnimation(
AnimationC: Animation,
baseEl: HTMLElement
): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
wrapperAnimation.addElement(baseEl.querySelector('.modal-wrapper'));
wrapperAnimation
.fromTo(
'transform',
'scaleX(0.1) scaleY(0.1)',
'translateX(0%) scaleX(1) scaleY(1)'
)
.fromTo('opacity', 0, 1);
backdropAnimation.fromTo('opacity', 0.01, 0.4);
return Promise.resolve(
baseAnimation
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(400)
.beforeAddClass('show-modal')
.add(backdropAnimation)
.add(wrapperAnimation)
);
}
Modify src/app/animations/leave.ts to reflect the following:
import { Animation } from '@ionic/core';
export function myLeaveAnimation(AnimationC: Animation, baseEl: HTMLElement): Promise<Animation> {
const baseAnimation = new AnimationC();
const backdropAnimation = new AnimationC();
backdropAnimation.addElement(baseEl.querySelector('ion-backdrop'));
const wrapperAnimation = new AnimationC();
const wrapperEl = baseEl.querySelector('.modal-wrapper');
wrapperAnimation.addElement(wrapperEl);
const wrapperElRect = wrapperEl!.getBoundingClientRect();
wrapperAnimation
.fromTo('transform', 'scaleX(1) scaleY(1)', 'scaleX(0.1) scaleY(0.1)')
.fromTo('opacity', 1, 0);
backdropAnimation.fromTo('opacity', 0.4, 0.0);
return Promise.resolve(baseAnimation
.addElement(baseEl)
.easing('ease-out')
.duration(400)
.add(backdropAnimation)
.add(wrapperAnimation));
}
Now we have an animation that will cause the modal to “pop” in and out of the screen. We have removed the beforeStyles
entirely as we don’t want the modal to be visible to begin with. Instead of supplying an initial opacity, we supply a second fromTo
that will animate the opacity of the modal from 0
to 1
over the course of the animation.
Rather than moving the modal on and off the screen with translate
, we are now using a scale
transform to shrink and grow the modal. This will cause the modal to animate from a very small size to its natural size, and then from it’s natural size to a very small size as it disappears from the changing opacity. We do still need to keep the translateX
on the enter animation as well to position the modal correctly.
We’ve also changed the duration
of the animation to be 400ms
, which is slightly longer than the other animation.
Summary
With just a few small changes, we are able to significantly impact the way the modal is animated onto and off of the screen. It is probably not all that often that you would need to use your own custom transition animations, but for circumstances where you do, this is a rather straight-forward and powerful way to do that.