Animations can be the element that sets an outstanding application apart from an average one, but overdone or poor performing animations can degrade the quality of an application. They can greatly improve user experience, or just make things harder for the user. In the web vs native debate, I give little credence to the blanket argument of native performance is better because it is rarely relevant – you don’t need the engine of a Ferrari when you never need to drive over 100kmph – but animations are one of the areas where the performance difference between a native and hybrid approach can be an important consideration.
In general, animations in hybrid applications (those that run inside of a web view, like Ionic) look great and run smoothly. Take the default animations in Ionic 2 as an example, the screen transitions and other animated elements are indistinguishable from a native application. However, animations within hybrid applications may become a problem when:
- You are targeting older Android devices
- You are doing a heavy amount of animation
In both of these cases, there may be a noticeable degradation in animation performance, and even Ionic’s own animations will struggle somewhat on some old Android devices.
I want this introduction, which I hope highlights the importance and difficulties of animations in hybrid mobile applications, to give context to the technology I will be talking about in this tutorial: the Web Animations API.
What is the Web Animations API?
The Web Animations API tries to unify the different approaches to web animations, providing the performance of CSS animations and the flexibility of JavaScript animations. It’s not going to magically solve all of your performance issues, but its unifying approach should make creating animations a lot easier.
It is still somewhat experimental and only has limited support, but there is a polyfill (basically a chunk of code that fills in support for the API in browsers that do not support it) available that you can include in your project. One of the browsers that currently does not support the Web Animations API is iOS Safari which is certainly an issue if you’re planning on building for iOS with Ionic, so we will be covering how to use that polyfill shortly.
If you want a way better introduction to the Web Animations API than I could ever give, check out Daniel Wilson’s Let’s talk about the Web Animations API.
For those of you short on time, I’ll just run through a quick example of using the Web Animations API in a normal Javascript application, and then we will take a look at how we can use the Angular 2 implementation in an Ionic 2 app.
Take the following CSS keyframe animation from this tutorial:
@-webkit-keyframes slideInBothWays {
0% {
transform: translate3d(-100%, 0, 0);
}
50% {
transform: translate3d(50%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
.slide-in-both-ways {
animation: slideInBothWays ease-in 1;
animation-duration: 750ms;
}
To apply the above animation all you need to do is give the slide-in-both-ways
class to an element. This would cause the element to animate in from the left of its current position, overshoot its final position to the right, and then come back to rest at its normal resting position.
To create the same animation using the Web Animations API, we would do something like this:
var elementToAnimate = document.getElementById('toAnimate').animate(
[
{ transform: 'translate3d(-100%,0,0)', offset: 0 },
{ transform: 'translate3d(-100%,0,0)', offset: 0.5 },
{ transform: 'translate3d(-100%,0,0)', offset: 1 },
],
{
duration: 750,
easing: 'ease-in',
}
);
The implementation is quite similar, the syntax is just a little different and we are using an offset
rather than the 0%
, 50%
, and 100%
keyframes that indicate the state the element should be in at various stages through the animation. For a more details explanation on how keyframe animations work, take a look at the full tutorial.
Now let’s move on to how we can make use of Angular 2’s implementation of the Web Animations API.
Before We Get Started
Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts. You must also already have Ionic 2 set up on your machine.
If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.
Using the Web Animations API with Angular 2
Angular 2 has built in support for the Web Animations API, where animations can be defined in a component’s metadata, but it is still in an experimental state, so if you do decide to use this method proceed with caution. I also believe that Ionic is adding in their own support for the Web Animations API but it is difficult to find information on that, so I’m not sure what’s happening there.
As I mentioned earlier, a polyfill is needed for these animations to work on browsers that don’t support the Web Animations API (iOS Safari included) – we will walk through adding that in our Ionic 2 application soon. In this tutorial, we will be building a simple Ionic 2 application that implements four different animations with Angular 2 animations.
Here’s what it will look like when we are done:
1. Generate a New Ionic 2 Application
Let’s get started by generating a new Ionic 2 application. We are going to be creating a simple one page application with some buttons, so we will just use the blank
template.
Run the following command to generate a new Ionic 2 application:
ionic start ionic2-animations blank --v2
2. Include the Polyfill
The polyfill that adds support for the Web Animations API can be found here. It’s important that you add this if you want your animations to work, but even if you don’t the animations will still fail gracefully (the transition will just happen instantly, rather than animating).
Download web-animations.min.js and web-animations.min.js.map and place them inside of a folder at src/assets/js (you will need to create the js folder)
Add the following line to your src/index.html file:
<script src="assets/js/web-animations.min.js"></script>
Now we’re all good to go!
3. Set up the Layout
Before we start creating some animations, we are going to set up a simple layout for our buttons with a bit of CSS styling to make it look nice. I’ll be using a bit of flexbox here, but not covering how it works, so if you’re interested in looking more into flex, take a look at this tutorial.
Modify home.html to reflect the following:
<ion-header>
<ion-navbar color="danger">
<ion-title> Animations! </ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<button ion-button color="light" (click)="toggleFlip()">Flip!</button>
<button ion-button color="light" (click)="toggleFlyInOut()">Fly Out!</button>
<button ion-button color="light" (click)="toggleFade()">Fade!</button>
<button ion-button color="light" (click)="toggleBounce()">Bounce!</button>
</ion-content>
Modify home.scss to reflect the following:
page-home {
.scroll-content {
background-color: #f55848;
display: flex !important;
flex-direction: column;
align-items: center;
}
button {
flex: 1;
width: 50%;
}
}
Aside from setting up some styles, we have created four buttons that make calls to various functions. These will eventually handle toggling the state of the animations, but more on that later!
4. Create the Animations
Now for the fun part – let’s create some animations. In Angular 2, we can define animations in the component’s metadata (inside its decorator), and link those animations to elements in our template. Before we can make use of the Web Animations API in Angular 2, we will need to import a few more classes from the Angular 2 library.
Modify your
imports
in home.ts to reflect the following:
import {
Component,
trigger,
state,
style,
transition,
animate,
keyframes,
} from '@angular/core';
import { NavController } from 'ionic-angular';
We will be making use of all of these in just a moment. I think it will be easiest to explain how the animations work if we have an example to look at, so let’s go ahead and define all of our animations right now.
Modify the decorator in home.ts to reflect the following:
@Component({
selector: 'page-home',
templateUrl: 'home.html',
animations: [
trigger('flip', [
state('flipped', style({
transform: 'rotate(180deg)',
backgroundColor: '#f50e80'
})),
transition('* > flipped', animate('400ms ease'))
]),
trigger('flyInOut', [
state('in', style({
transform: 'translate3d(0, 0, 0)'
})),
state('out', style({
transform: 'translate3d(150%, 0, 0)'
})),
transition('in > out', animate('200ms ease-in')),
transition('out > in', animate('200ms ease-out'))
]),
trigger('fade', [
state('visible', style({
opacity: 1
})),
state('invisible', style({
opacity: 0.1
})),
transition('visible <> invisible', animate('200ms linear'))
]),
trigger('bounce', [
state('bouncing', style({
transform: 'translate3d(0,0,0)'
})),
transition('* > bouncing', [
animate('300ms ease-in', keyframes([
style({transform: 'translate3d(0,0,0)', offset: 0}),
style({transform: 'translate3d(0,-10px,0)', offset: 0.5}),
style({transform: 'translate3d(0,0,0)', offset: 1})
]))
])
])
]
})
We’ve added an animations
array to the metadata now, which defines four different animations using trigger
: flip
, flyInOut
, fade
, and bounce
.
The trigger
is what we will use to link the animation to an element. In the case of the flip
animation, we would attach a @flip="state"
to the element in our template that we want to use this animation on. The state
in the code above defines a set of properties for the element, and these states are what we will be animating to and from. So in the case of the flip
animation we have a flipped
state that will rotate the element 180 degrees and also change its background color. If we quickly take a look at the eventual syntax in our template:
<button @flip="flipState" light (click)="toggleFlip()">Flip!</button>
you will see that @flip
is referencing a flipState
value. This flipState
will be defined in home.ts and it is what sets the state of the animation. So since we have a state of flipped
defined inside of the flip
animation trigger, if we set the flipState
variable to flipped
it will activate the animation. If we change flipState
to anything else the animation won’t be triggered.
In the metadata, after we define a state
(or states), we define the transition
– this defines how the animation should occur. The first parameter:
'* > flipped'
defines when the animation should occur. Here we are saying that the animation should occur “when flipState
changes from any value (*
) to flipped". When that happens, we use
animateto define that it should animate over
400msand use the
easetiming function. We don't have any animation defined for when the state changes from
flipped` to something else, i.e:
'flipped > *'
so if we change flipState
to flipped
we will see the animation occur, but if we change flipState
back to something else, it will immediately go back to its original values instead of animating back:
Now that we have a general understanding of the functions being used here, let’s more briefly go through the rest. The next animation flyInOut
defines two states, an in
where the element will be in its original position, and an out
where it will be 150% to the right. We also define two transitions, one from in
to out
and one from out
to in
. By defining both of these transitions, the element will animate both when its state is changed to in
and out
. Notice that one transition uses the ease-in
function, and the other uses ease-out
.
The fade
animation is similar, except that it animates the opacity
of the element with a visible
and invisible
state instead (although, the invisible state is still technically a little bit visible). In this case though, we are using the same transition both ways, 200ms with the linear
function, so we can use some shorthand for defining the transition:
transition('visible <> invisible', animate('200ms linear'))
this is equivalent to:
transition('visible > invisible', animate('200ms linear')),
transition('invisible > visible', animate('200ms linear'))
or
transition('visible > invisible, 'invisible > visible', animate('200ms linear'))
Our last animation, bounce
, is a little trickier. We are moving the position of the element to create a bounce
effect, but there are multiple states to the animation. Just like we use percentage based keyframes in the CSS animation we looked at earlier, we also use keyframes
here, except we define the style
at certain points throughout the animation using offset
instead. Let’s take another look at the transition we are defining:
transition('* > bouncing', [
animate(
'300ms ease-in',
keyframes([
style({ transform: 'translate3d(0,0,0)', offset: 0 }),
style({ transform: 'translate3d(0,-10px,0)', offset: 0.5 }),
style({ transform: 'translate3d(0,0,0)', offset: 1 }),
])
),
]);
We have three keyframes here, which have an offset of `,
0.5, and
1– these are equivalent to
0%,
50%, and
100%in the CSS keyframe animation. So at the beginning of the animation, the element will be in its starting position, halfway through it will be
10px` above its starting position, and at the end of the animation it will be back to its starting position again.
Now that we have all of our animations defined and explained, let’s finish off the rest of the application.
Modify home.html to reflect the following:
<ion-header>
<ion-navbar color="danger">
<ion-title> Animations! </ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<button [@flip]="flipState" ion-button color="light" (click)="toggleFlip()">
Flip!
</button>
<button
[@flyInOut]="flyInOutState"
ion-button
color="light"
(click)="toggleFlyInOut()"
>
Fly Out!
</button>
<button [@fade]="fadeState" ion-button color="light" (click)="toggleFade()">
Fade!
</button>
<button
[@bounce]="bounceState"
ion-button
color="light"
(click)="toggleBounce()"
>
Bounce!
</button>
</ion-content>
Now we’re using that @trigger
syntax to attach all of our animations to different buttons. We will also be able to control what state, and thus which animation, is applied to each element with flipState
, flyInOutState
, fadeState
, and bounceState
. We will need to define these in home.ts, as well as the functions that will handle toggling them.
Modify the class in home.ts to reflect the following:
export class HomePage {
flipState: String = 'notFlipped';
flyInOutState: String = 'in';
fadeState: String = 'visible';
bounceState: String = 'noBounce';
constructor(public navCtrl: NavController) {
}
toggleFlip(){
this.flipState = (this.flipState == 'notFlipped') ? 'flipped' : 'notFlipped';
}
toggleFlyInOut(){
this.flyInOutState = 'out';
setInterval(() > {
this.flyInOutState = 'in';
}, 2000);
}
toggleFade() {
this.fadeState = (this.fadeState == 'visible') ? 'invisible' : 'visible';
}
toggleBounce(){
this.bounceState = (this.bounceState == 'noBounce') ? 'bouncing' : 'noBounce';
}
}
Initially, we have our states set to:
- notFlipped
- in
- visible
- noBounce
So all of the buttons will be in their “normal” state. Both in
and visible
are actually states with styles that we have defined in our animations, so they will take on those styles by default (not that it is noticeable), but we don’t have any animation states for notFlipped
and noBounce
so they will just display however they normally would by default.
The toggle functions we have defined here simply handle switching the state variables from one thing to another. If you activate the toggleFlip
function for example, flipState
will be changed from notFlipped
to flipped
so the flipped
state in the flip
animation will trigger (that was a mouthful).
Both toggleFade
and toggleBounce
behave similarly, but toggleFlyInOut
is a little different. Since we are animating the element off screen, we add a setInterval
to change the elements state back to in
after 2 seconds so that it comes back on screen again.
If everything has gone to plan, you should now have something that looks like this:
Summary
There’s more to animation in Angular 2 that I haven’t covered here, so please check out this great resource for more information.
I haven’t had a lot of time to play around with Angular 2 animations yet, so I’m not sure on the limitations and how far it has to go to be completed, but the initial impression is great. I love the concept of the Web Animations API so it’s great that we can make use of that, and I find the method for defining animations to be very well structured and organised.
In a future tutorial, I plan on using these Angular 2 animations to create a more “real world” animations example (a pretty login screen with nice animations perhaps).