Aurelia

Animating Apps with Aurelia - Part 1

Introduction

Aurelia

Aurelia


Animating Apps with Aurelia - Part 1

Posted by Aurelia on .
Featured

Animating Apps with Aurelia - Part 1

Posted by Aurelia on .

This week core team member Vildan Softic shares with us how the Aurelia animation system works as well as how you can get started using animation in your apps today.


Simple, yet effective animations can bring your app to life. Remember the ominous <blink> tag? That was clearly too much. In this article we'll explain how Aurelia's animation architecture is designed. We'll then leverage the official Animator-CSS plugin to see how to create subtle animations. In a followup article, we'll introduce a more sophisticated implementation, built upon VelocityJS.

How does Aurelia support animations?

A key goal in building animation support for Aurelia was to enable a flexible solution that allows you to choose whatever library you like. As a result, you're neither limited to a proprietary API nor to a certain style for how to implement your animations.

To enable this flexibility, Aurelia's animation system is built around a simple interface, which is part of the templating repository:

export class Animator {  
    static configureDefault(container, animatorInstance);

    move(element:Element):Promise;
    enter(element:Element):Promise;
    leave(element:Element):Promise;

    removeClass(element:Element, className:string):Promise;
    addClass(element:Element, className:string):Promise;

    animate(element:Element, className:string, options:Object);
    runSequence(sequence:string):Promise;

    /** (for JS based animators) **/
    registerEffect(effectName:string, properties:Object);
    unregisterEffect(effectName:string);
}  

https://github.com/aurelia/templating/blob/master/src/animator.js

As you can see, the interface just declares available animation methods which return promises. Also note the configureDefault method which is used to register a default instance of the Animator with the framework.

The Animator interface methods are called from various places within the framework, most notably the templating engine's ViewSlot class. The ViewSlot is used by the framework to abstract the idea of a "location" or "slot" in the DOM where views can be added or removed. This abstraction allows the framework to enforce lifecycle hooks, play animations and more. The following listing depicts parts of the ViewSlot.add function, showing how a View is added into the DOM:

var element = view.firstChild ? nextElementSibling(view.firstChild) : null;  
if(view.firstChild &&  
  view.firstChild.nodeType === 8 &&
  element &&
  element.nodeType === 1 &&
  element.classList.contains('au-animate')) {
  this.animator.enter(element);
}

https://github.com/aurelia/templating/blob/master/src/view-slot.js#L75-L82

What you see here is that Aurelia essentially calls the proper methods at various positions in the code base and thus hints the execution of potential animations. We say hints because of two reasons explained next.

Exchangeable Implementations

The default implementation of Animator does not do anything besides immediately resolving a promise. The reason for this is to not burden your app with something you might not need at all while still supporting a base for other implementations. Those other implementations can be provided as Aurelia Plugins. Throughout the rest of this article we're going to take a look at the official Plugin leveraging CSS3-Animations, but know that there are already several other implementations available and many more to come.

Opt-In Animations

Even when you use animations, you might just want to use them only for specific elements. This is very true for business applications, where animations are rarely used and only need to highlight specific actions. In order to provide an efficent mechanism for Aurelia, preventing it from needing to observe every potential element, you must be specific about which elements should be animated. This is done by placing a special class called au-animate on your desired element.

<section class="au-animate">  

If you take a look at the previous ViewSlot.add function, you'll see that it checks whether the inspected element does contain the class au-animate and only then continues firing the respective animation method.

A Concrete Implementation with CSS-Animations

There are multiple ways to create animations of all kinds, whether by leveraging large Frameworks like Famo.us or GreenSock, more specific libraries like Velocity.js or even simple jQuery wrappers like Transit. Each of them focus on providing animations via JavaScript code. This approach offers you the most flexibility and control over how your animations are executed. But sometimes it feels like a lot of overhead if all you want is to change the color or the visibility of an element. So instead of touching the code base, an alternative approach is to use CSS3-Animations, which are completely handled by CSS-Markup.

As a rule of thumb you would use the CSS-Animator if:

  • all you need are subtle and sparse animations

  • you don't want to touch an existing code base

  • you'd like to utilize existing CSS3 code snippets

Please note that the CSS-Animator only supports animations through the animation property and NO transitions. Be assured though, that there are tons of possibilities you can achieve with CSS-Animations alone. Take a look at the following tutorials for more info: W3School, TutsPlus

How the Animation Process Works

Essentially, all that is needed to make an animation work is to define CSS classes with special predefined suffixes. You get the chance to use preparation classes, added before the actual animation starts as well as activation classes, used to trigger the actual animation. Take a look at the following table for all available options.

Method Description Preparation Activation
Enter Element enters the DOM au-enter au-enter-active
Leave Element leaves the DOM au-leave au-leave-active
addClass Adds a CSS class n/a [className]-add
removeClass Removes a CSS class n/a [className]-remove

In order to understand how the CSS-Animator works, lets take a look at the enter method. We'll just highlight the most important parts but you can find the full source code here.

All animation methods are supposed to return a Promise in order to allow async execution of logic after the animation completes.

enter(element) {  
    return new Promise((resolve, reject) => {
    ...

Next, a unique animation id is created to identify the current animation:

// Step 1: generate animation id
var animId = element.toString() + Math.random(),  
    classList = element.classList;
    ...

In order to allow the developer to setup CSS-properties before the actual animation runs, the Animator will add the class au-enter to the element.

//Step 2: Add animation preparation class
classList.add('au-enter');  
...

To handle the animation flow, the Animator will listen for the animationstart event, fired on the specific element. Notice the use of multiple events since browsers may fire vendor-specific events.

// Step 3: setup event to check whether animations started
var animStart;  
this._addMultipleEventListener(  
    element, 
    "webkitAnimationStart animationstart", 
    animStart = (evAnimStart) => {
    ...

Now that we know that the animation actually started, add it to the stack and start listening for its completion.

// Step 3.1: Animation exists, put on stack
this._addAnimationToStack(animId);

// Step 3.2: Wait for animation to finish
var animEnd;  
this._addMultipleEventListener(  
    element,
    "webkitAnimationEnd animationend",
    animEnd = (evAnimEnd) => {
    ...

When the animation ends, remove the activation/preparation classes and the animation id from the stack. Then resolve the promise with true.

// Step 3.2.1: remove animation classes
classList.remove('au-enter-active');  
classList.remove('au-enter');

// Step 3.2.2 remove animation from stack
this._removeAnimationFromStack(animId);  
...          
resolve(true);  
...

In order to get the animation triggered, the animator adds the activation class.

// Step 4: trigger animation      
...      
classList.add('au-enter-active');  
...

But perhaps that class is not defined in your stylesheet. In that case the Animator will wait for a certain timeout to run out and cleanup the element classes, resolving the promise with false to indicate that no animation happened.

// Step 5: if no animations happened cleanup animation classes
setTimeout(() => {  
    if (this.animationStack.indexOf(animId) < 0) {
        classList.remove('au-enter-active');
        classList.remove('au-enter');
        ...
        resolve(false);
    }
}, this._getElementAnimationDelay(element) + this.animationTimeout + delay);

Working with Default Animation Triggers

Enough theory. Let's get our hands dirty and create a simple list of elements, where each element is going to be animated separately. We start out with the following VM. It contains an array of strings which is going to be rendered and a function to remove a specific animator from that list.

export class AnimationDemo{  
    animators = [
        'Base-Animator',
        'CSS-Animator',
        'Velocity-Animator',
        'TinyAnimate-Animator',
        'GreenSock-Animator'
    ];

    removeAnimator(animator) {
        var index = this.animators.indexOf(animator);
        this.animators.splice(index, 1);
    }
}

The view is rendered by using an unordered-list. We'll give it the class aurelia-animators to reference it later in our stylesheet. Now we scaffold each animator as a list-item. First we need to give them the class au-animate to tell Aurelia that those elements can be animated. Inside each item we display an icon, bound to the removeAnimator function.

<template>  
    <section>
        <ul class="aurelia-animators">
            <li class="au-animate" repeat.for="anim of animators">
            <i class="fa fa-trash" click.delegate="$parent.removeAnimator(anim)"> </i> ${anim}</li>
        </ul>
    </section>
</template>  

Finally we need to implement the animations by creating the necessary CSS-classes. We start off first with some general design of the list. Next we define the enter animations for each item. To do so we utilize the selector .aurelia-animators>li.au-enter added automatically during the preparation phase. Since some browsers may not accept having new element enter invisible we should force it by using the !important override. The actual animation will be fired by adding the au-enter-active class to the element. In there, we leverage the animation property and its vendor-specific pendant, referencing a fadeIn-animation with a duration of two seconds. We do the same for the leave animation but may skip the preparation class and only define au-leave-active. Last but not least, we create the Animation-Keyframes and tell it to animate the element's opacity.

.aurelia-animators {
    padding: 20px;
    list-style: none;
}

.aurelia-animators>li.au-enter {
    opacity: 0!important;
}

.aurelia-animators>li.au-enter-active {
    -webkit-animation: fadeIn 2s;
    animation: fadeIn 2s;
}

.aurelia-animators>li.au-leave-active {
    -webkit-animation: fadeOut 2s;
    animation: fadeOut 2s;
}

/* CSS3-Animations */
@-webkit-keyframes fadeIn {
    0%   { opacity: 0; }
    100% { opacity: 1; }
}

@keyframes fadeIn {
    0%   { opacity: 0; }
    100% { opacity: 1; }
}

@-webkit-keyframes fadeOut {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

@keyframes fadeOut {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

So we end up with the following result.

Now that's nice but wouldn't it be better if all of the items would get rendered one after each other instead of in parallel? Those kind of animations are called staggered animations, where a series of elements is simply executed with a short timeout in between each one. The CSS-Animator does this by adding a class au-stagger to the parent of the elements.

<ul class="aurelia-animators au-stagger">  

Additionally, we need to define a new CSS-class called, you guessed it au-stagger and in there define the timeout to be used in between each item.

.aurelia-animators.au-stagger {
    -webkit-animation-delay:50ms;
    animation-delay:50ms;
}

And the result looks like this.

Implementing Custom Animation Triggers

We've seen how to animate using pure CSS. But sometimes you'd like to trigger animations after certain actions. To do so, you can access all the animation methods via JavaScript inside your VM as well. In order to show that, lets animate the background of the list. We're adding a new button which is going to trigger the function animateBackground.

<button click.delegate="animateBackground()">Animate Background</button>  

Inside our VM we first need to request the animator and inject it into the constructor. We'll then store it for later use alongside the view's DOM-Element in the animateBackground method. When animating the background, we first get the list. Now we can call the animate Method of the CSS-Animator. It expects the element as first parameter, followed by a className which it's going to add and after successful animation remove afterwards. What happens behind the scenes is that animate just acts as an alias, which will call the methods addClass and removeClass sequentially.

import {CssAnimator} from 'aurelia-animator-css';  
import {inject} from 'aurelia-framework';

@inject(CssAnimator, Element)
export class AnimationDemo{  
    constructor(animator, element) {
        this.animator = animator;
        this.element = element;
    }

    ...

    animateBackground() {
        var list = this.element.querySelector('.aurelia-animators');
        this.animator.animate(list, 'background-animation');
    }
}

This gives us a chance to define a two-fold animation, when the class is added and when it's removed. We're actually not even forced to define the class background-animation itself but just its activation classes, which are shown next. Note how we use a more detailed KeyFrame definition this time, to switch through multiple background-colors.

.background-animation-add {
    -webkit-animation: changeBack 2s;
    animation: changeBack 2s;
}

.background-animation-remove {
    -webkit-animation: fadeIn 2s;
    animation: fadeIn 2s;
}

@-webkit-keyframes changeBack {
    0% { background-color: green; }
    25% { background-color: red; }
    50% { background-color: blue; }
    75% { background-color: violet; }
    100% { background-color: yellow; }
}

@keyframes changeBack {
    0% { background-color: green; }
    25% { background-color: red; }
    50% { background-color: blue; }
    75% { background-color: violet; }
    100% { background-color: yellow; }
}

And again, here is the result.

Summary

We hope you enjoyed this introduction to working with Animation in the Aurelia Framework. There is much more to be told though. We'll continue this discussion in part 2 where we'll be presenting an alternative implementation via a JS-based Animator. Now go out and animate all the things! If you encounter any problems join us in our gitter channel.

View Comments...