Ionic 3 Custom Transitions Even More Native

author: Rafał Radziszewski / last edit: 03-12-2017

We all like the Ionic page transitions, they already makes us feel like we're using a native app. Especially when the WKWebView has became a default WebView. Yes, the WKWebView comes with a great performance, so you should immediately switch to use that, if you didn't have a chance.

Ionic's default transitions are okay, but let's create an unique one!

Page switch animation is one of those things users of our app experience more than a often. We - developers - should keep in mind to still keep them simple and fast. Because of huge amount of customization possibilities it's easy to cross the line and go too far.

About these "customization possibilities"...

We can do pretty much everything, because it's mostly about adding the CSS styles to specific DOM element. You can set the animation for the whole page (like the default slide-right-left transitions), but you can also set the animation for each child of that page. It's all up to you and your demands.

Just imagine the scenario where you trigger the page change, the old page gets blurred and the new page's header just flows in from the top and then, separately, the rest of the content flows in from bottom. Sounds amazing? I think it sounds bad-ass. This scenario is very possible and I'll show you a good way to do that.

Check how it looks in slow motion:

Since framework's version RC0 it is quite easy to do. The steps are:

Names of the files, classes, variables etc. that I'll use may correspond to the Story App.

Step 1 - Create the class that handles animation

First, I'd prefer to create the folder transitions in our src catalogue - we can then keep all of our custom transition classes in one place. I believe it might be better for further reusability.

Now go to that folder and create a TypeScript file. I called mine BookPageTransition.ts, because the animation is going to be visible while entering the "book" details (Story App).

Next, let's create a class and import the required stuff.

import { Animation } from 'ionic-angular/animations/animation';
import { PageTransition } from 'ionic-angular/transitions/page-transition'

export class BookPageTransition extends PageTransition {

    init() {
        super.init();
        const that = this;
    }
}

In the code above I'm importing the Animation and PageTransition classes from ionic-angular. The first one is used to handle the animation for each DOM element, while the second one is used to extend our BookPageTransition class to make it able to be registered later as a transition.

Okay, now let's declare some vars:

init() {
    super.init();
    const that = this;
    const enteringView = that.enteringView.pageRef();
    const leavingView = that.leavingView.pageRef();
}

I think the naming already says a lot. I'm declaring a variables which hold the references to the page we're leaving and the page we're entering.


console.log of the enteringView variable.
The ElementRef gives us a direct access to the DOM element

Let's go further!

init() {
    super.init();
    const that = this;
    const enteringView = that.enteringView.pageRef();
    const leavingView = that.leavingView.pageRef();

    const leavingViewElAnimation = new Animation(that.plt, leavingView.nativeElement);
}

This one need explanation.

I just declared another new variable - an instance of previously imported Animation class.

Animation class is something like "actions provider". It's purpose is to manage what is and how it is being animated.

Animation class allows us to:

and more...

new Animation(this.plt, leavingView.nativeElement);

Now, let's finally animate something! Put this code after our previous animation's declaration:

leavingViewElAnimation
      .beforeStyles({ filter: 'blur(5px)' })
      .afterClearStyles(['filter']);

Technically that's the simplest animation we can imagine, but even this already gives us a nice visual effect.

The beforeStyles() takes an object of CSS styles that should be applied before the animation begins.
The afterClearStyles() takes an array of style names that were applied before and should be removed in the end.
In summary, the code above first sets the blur filter to the page that is being left and then, after the new page has been loaded, removes the filter.

Hope you're still there, because we have only one more action to do in this step.

that.add(leavingViewElAnimation);

Don't forget about that line, because the animation won't fire without it!

Okay, so the biggest step is done. We have a fully working transition ready to become registered and used.

Optional

Check out my full implementation of two animations that contains more options! Find out if there's something interesting for you.

import { Animation } from 'ionic-angular/animations/animation';
import { PageTransition } from 'ionic-angular/transitions/page-transition'

export class BookPageTransition extends PageTransition {

    init() {
        super.init();

        const that = this;
        const enteringView = that.enteringView.pageRef();
        const leavingView = that.leavingView.pageRef();
        const animation = new Animation(that.plt)

        const scrollContentElAnimation = new Animation(this.plt, enteringView.nativeElement.querySelector('.scroll-content'));

        const topStyles = { transform: 'translateY(-200px)' };

        const leavingViewElAnimation = new Animation(that.plt, leavingView.nativeElement);
        const enteringViewElAnimation = new Animation(that.plt, enteringView.nativeElement.querySelector('ion-content'));
        const headerElAnimation = new Animation(that.plt, enteringView.nativeElement.querySelector('ion-header.header'));
        const bookDetailsElAnimation = new Animation(that.plt, enteringView.nativeElement.querySelector('.book-cover'));
        const bookContentElAnimation = new Animation(that.plt, enteringView.nativeElement.querySelector('.bookPage-readMode'));

        leavingViewElAnimation
            .beforeStyles({ filter: 'blur(5px)' })
            .afterClearStyles(['filter']);


        enteringViewElAnimation
            .beforeStyles({ backgroundColor: 'transparent', backgroundImage: 'none' });

        headerElAnimation
            .fromTo('transform', topStyles.transform, 'translateY(0)')
            .afterClearStyles(['transform']);

        bookDetailsElAnimation
            .fromTo('transform', topStyles.transform, 'translateY(0)')
            .afterClearStyles(['transform']);

        bookContentElAnimation
            .fromTo('backgroundColor', 'transparent', 'white')
            .fromTo('transform', 'translateY(1000px)', 'translateY(0)')


        scrollContentElAnimation
            .beforeStyles({ 'overflow': 'visible'})
            .afterClearStyles(['overflow']);


        that
            .duration(500)
            .add(leavingViewElAnimation)
            .add(enteringViewElAnimation)
            .add(scrollContentElAnimation)
            .duration(400)
            .add(bookContentElAnimation)
            .add(headerElAnimation)
            .add(bookDetailsElAnimation);
    }

}

Step 2 - Register the created class as transition

This one is pretty fast and much easier.

What we need to do is to let Ionic know that our newly created transition class should be treated as actual transition. In other words, we need to register it (so it can be used later).

To do that, let's head to our main module - app.module.ts.

import { Config } from 'ionic-angular';
import { BookPageTransition } from './transitions/BookPageTransition';

...

export class AppModule {

  constructor(config: Config) {
    config.setTransition('book-page-transition', BookPageTransition);
  }

}

First thing here is the Config import. As the Ionic source says:

"The Config lets you configure your entire app or specific platforms. You can set the tab placement, icon mode, animations, and more here."

What we're after is its setTransition method. It registers the transition for further use - with the name of first parameter and the transition class of the second.

Step 3 - Assign the registered transition to page change action

The last step is to tell the navigation which transition should it use.

We're doing that by specifying the animation in the nav options object (third argument of navCtrl.push()).

...

that.navCtrl.push(BookPage, { id: bookData.id }, { animation: 'book-page-transition' });

...

That's it! Your new transition should now work!

Good to have - export the name of transition

It's good to declare and export a const that holds the name of the transition. Something like this:

...

import { PageTransition } from 'ionic-angular/transitions/page-transition'

export const BOOK_PAGE_TRANSITION_NAME = 'book-page-transition';

export class BookPageTransition extends PageTransition {

...

This solution eliminates the need of change couple of files, if we ever decide to change the name. It could be implemented just like below:

...

import {BookPageTransition, BOOK_PAGE_TRANSITION_NAME} from "./transitions/BookPageTransition";

...

config.setTransition(BOOK_PAGE_TRANSITION_NAME, BookPageTransition);

...

that.navCtrl.push(BookPage, { id: bookData.id }, { animation: BOOK_PAGE_TRANSITION_NAME });

...
Read more!