Web Animation API with Ionic

Animating from the Void: Enter and Exit Animations in Ionic



·

Last week I wrote a tutorial that covered how to create a simple add to cart animation using the Angular animations library (which is based on the Web Animations API).

To quickly recap, creating animations with the Angular animations library involves creating “triggers” with “states” that define a set of styles and then defining “transitions” between those states. The state is associated with a trigger attached to an element, like this:

<my-element [@myTriggerState]="something">

A state can be any string value that we define. We might use values like active and inactive or idle or inprogress and so on. We then define transitions by using matchers like this:

active => inactive

or

inactive => *

Using this method, we can easily target the specific state transition that we want to target. This is a problem when trying to create entry and exit animations for elements – perhaps we want an item in a list to fly in from the left when it is added. We can’t associate a state with something that doesn’t exist yet.

In this tutorial, we will be discussing how we can use the special void state to create entry and exit animations. We will be creating an animation that will cause items to fly in from the left, and when they are deleted they will fly out to the right:

Void Animation in Ionic

If you do not already have a basic understanding of creating animations with the Angular animations library, I would recommend reading the previous tutorials first.

Before We Get Started

Last updated for Ionic 3.6.0

Before you go through this tutorial, you should have at least a basic understanding of Ionic concepts. You must also already have Ionic set up on your machine.

If you’re not familiar with Ionic already, I’d recommend reading my Ionic Beginners Guide or watching my beginners series first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic, then take a look at Building Mobile Apps with Ionic.

Setting up Angular Animations

There are a few dependencies that need to be installed in order to be able to use the Angular animations library in your application. For details on how to set that up in an Ionic application, please take a look at the previous tutorial.

The Void State

To understand how to create enter and exit animations for elements, we need to understand a special state called void.

The void state is the state that an element will be in when it does not exist in the DOM. If you wanted to create an animation that applies when a particular element is added to the application, you couldn’t define the appropriate state transition because it doesn’t exist yet and so it has no associated state. Using the special void state allows us to create enter and exit animations.

The void state is a bit awkward because you might want to define some styles for the void state like this:

trigger('itemState', [
    state('void', style({
        transform: 'translateX(-100%)'
    })),
    transition('void => *', animate('500ms ease-out'))
])

This is basically saying that when an element that this trigger is associated with is in the void state it should be moved 100% to the left of its current position (e.g. off the screen to the left). Then we have a transition for void => * so that the animation will apply as soon as the element is added to the application (since it will no longer have the void state).

This is fine, and it will work, but consider that if we have this void animation that will cause the element to come into the screen from the left, what if we wanted that element to fly off to the right of the screen when it is removed? When the element is removed it will be given the void state again, but the style we have defined for the void state moves the element off to the left of the screen, not the right. We need different styles to apply depending on whether the element is being added:

void => *

or the element is being removed:

* => void

Although we can create states with specific styles, we can also define style directly on transitions as well. This means that we could do this:

trigger('itemState', [
    transition('void => *', [
        style({transform: 'translateX(-100%)'}),
        animate('500ms ease-out')
    ]),
    transition('* => void', [
        animate('500ms ease-in', style({transform: 'translateX(100%)'}))    
    ])
])

This defines both an enter and a leave animation. For the void => * transition, which is the enter transition, we add a style that moves the element off to the left of the screen. The exit transition, which is * => void, looks slightly different. We have styling that will move the element off to the right of the screen, but we add the style inside of the animate method. This is because we don’t want the element to have that style at the beginning of the transition, we want to animate to that style from whatever styles it currently has.

Creating the Animation

We are going to use that void state now to finish implementing the animation we have been discussing.

Add the following animation to the @Component metadata:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { trigger, state, style, animate, transition } from '@angular/animations';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html',
    animations: [
        trigger('itemState', [
            transition('void => *', [
                style({transform: 'translateX(-100%)'}),
                animate('500ms ease-out')
            ]),
            transition('* => void', [
                animate('500ms ease-in', style({transform: 'translateX(100%)'}))    
            ])
        ])
    ]
})
export class HomePage {

    items: string[] = [];

    constructor(public navCtrl: NavController) {

    }

    addItem(){
        this.items.push('test');
    }

    deleteItem(item){
        this.items.splice(this.items.indexOf(item), 1);
    }

}

We have created two transitions inside of the itemState trigger. The first handles the case for an element being added to the DOM, from “the void” to any state. We apply a style that will transform its position off to the left side of the screen, and we define how we want the animation to run. The second transition, from any state to “the void”, defines the styles inside of the animate method because we want to animate to that style.

I’ve also included some test code that you can use to handle adding items to an array and deleting them.

Adding the Animation to List Items

Now that we have our trigger defined, we just need to associate it with the list items:

Attach the trigger to an element

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Void Animations
    </ion-title>
    <ion-buttons end>
        <button ion-button icon-only (click)="addItem()"><ion-icon name="add"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>

    <ion-list no-lines>
        <ion-item @itemState (click)="deleteItem(item)" *ngFor="let item of items">
            Cold is the void...
        </ion-item>
    </ion-list>

</ion-content>

Since we are only creating enter and exit animations, there is no need to associate the trigger with a state, so we don’t need to set up a property binding like we usually would, i.e:

<ion-item [@itemState]="someVar" (click)="deleteItem(item)" *ngFor="let item of items">

I’ve also included the rest of the test code so that you can click the button in the navbar to add new items (and clicking any item will also trigger a delete). If you were to give this a test now, you should have something like this:

Void Animation in Ionic

Summary

The Angular animations syntax can be a bit to get your head around at first, but once you understand how to use the void state it is a very simple way to create entry and exit animations in your Ionic applications.

What to watch next...

  • Yasir

    Hi Josh, when removing an item, it does not remove the clicked item! it keeps removing the last index item even though its not the item that’s been clicked.

    • That’s because all the items in this case are identical, if you were using this in a real scenario the indexOf method would work. If you really wanted to make this specific example work properly, it would probably be easiest to pass the index of the item from the template directly to the delete function (rather than passing it the item).

  • kevin zana

    Thanks Josh for this excellent and clear tutorial !! I just have 1 question : is it possible to declare some animations globally and use them in each components (with inheritance or something like that)? Thank you a lot.

  • passionate_prog

    hi Joshua, can this animation be applied to ngFor in an ion-list? the items appear one after the other after a delay. Thanks for the advise?

  • passionate_prog

    hi. I am trying to do it with a list of objects already filled but when loading the page, it does not come one after the other like some delay. Is that possible to do? I know there is an api called stagger in angular that can be used to do that but unfortunately this can be done in angular 4.3.3. Can this be done in angular 4.1.3? Thanks for your advise.

  • Another great article, my question is if my list has some data reading from http for example, how can I make it staggered and comming one by one after a short delay (something like your animated gif but automatically not by clicking add)?