Advanced CSS3 animations with Sencha Touch


Touch has some nice CSS3 animations like slide, flip, fade, pop and cube…but what if you need something different, like a custom slide-fade animation?

I’ve covered how to use animations in Using CSS3 animations on Sencha Touch and Animate components in a view, but I didn’t cover the internals of the framework, nor how can you create a custom animation. So let’s take a look at it, step by step.

Let’s first clarify some of the concepts. Ext.Animator and Ext.Anim classes are different. While Ext.Animator provides full animations for components, Ext.Anim provides a much simpler approach for DOM elements. So don’t expect to slide a container out of the screen with Ext.Anim and have the hide event fire only when the animation is over. This is responsability of Ext.Animator. Animator for components, Anim for simple DOM animations.

Ext.Animator

As I said, Ext.Animator is responsible for components animations. It will pause events like hide, and resume when the components is totally hidden, and so on. It has 3 key packages: Runner, Animations and Layout.

Runner


If you have an animation class, you need a runner class to execute it. That’s why we have the base class Ext.fx.runner.Css, that is extended between Ext.fx.runner.CssAnimation and Ext.fx.runner.CssTransition. Currently only CssTransition is in use, while CssAnimation class is still under development. The class responsible for exposing these runners is Ext.fx.Runner:

Ext.define('Ext.fx.Runner', {
    requires: [
        'Ext.fx.runner.CssTransition'
//        'Ext.fx.runner.CssAnimation'
    ],

    constructor: function() {
        return new Ext.fx.runner.CssTransition();
    }
});

You can see that Ext.fx.Runner is pretty simple, and currently only returns an instance of Ext.fx.Runner.CssAnimation.

Going even deeper, if you look the source code of Ext-more.js you’ll notice something like this:

animator: {
	xclass: 'Ext.fx.Runner'
},

//and

if (data.animator) {
	Ext.Animator = data.animator;
}

It means that when the framework starts, it will create an instance of Ext.fx.Runner and hold it with the reference Ext.Animator. This is what the frameworks uses for running its animations, Ext.Animator.run().

This is the first piece of Touch Fx package, the runner. Let’s move to Animation classes.

Animation


Ok, we have a class that runs animations, now we need classes that define those animations. It all starts with the abstract class Ext.fx.animation.Abstract, and its extensions Ext.fx.animation.* : Cube, Fade, Flip, Pop, Slide, Wipe.

These classes have the simple task of define the 2 states of a animator: from and to. Take a look on this snippet for Ext.fx.animation.Pop

getData: function() {
    var to = this.getTo(),
        from = this.getFrom(),
        out = this.getOut();

    if (out) {
        from.set('opacity', 1);
        from.setTransform({
            scale: 1
        });

        to.set('opacity', 0);
        to.setTransform({
            scale: 0
        });
    }
    else {
        from.set('opacity', 0);
        from.setTransform({
            scale: 0
        });

        to.set('opacity', 1);
        to.setTransform({
            scale: 1
        });
    }

    return this.callParent(arguments);
}

If the animation is “out”, which means the popOut animation, the opacity and scale will go from 1 to 0, and the inverse if the animation is a popIn, going visible on the screen.

You can look other classes like Fade, Scale and so on, and verify that they are pretty simple. This is the key for creating our custom “Slide-Fade” animation.

Ext.define('Ext.fx.animation.ScaleFade', {
    extend: 'Ext.fx.animation.Abstract',
    alternateClassName: 'Ext.fx.animation.ScaleFadeIn',
    alias: ['animation.scalefade', 'animation.scaleFadeIn'],

    config: {
        out: false,
        before: {
            display: null,
            opacity: 0
        },
        after: {
            opacity: null
        },
        reverse: null
    },

    updateOut: function(newOut) {
        var to   = this.getTo(),
            from = this.getFrom();

        if (newOut) {
            from.set('opacity', 1);
            from.setTransform({
                scale: 1,
                translateY: 0
            });
            
            to.set('opacity', 0);
            to.setTransform({
                scale: 1.2,
                translateY: 100
            });
        } 
        else {
            from.set('opacity', 0);
            from.setTransform({
                scale: 1.2,
                translateY: 100
            });
            
            to.set('opacity', 1);
            to.setTransform({
                scale: 1,
                translateY: 0
            });
        }
    }
});

Ok, we have a runner and an animation. We are already able to show a component with animation doing something like this:

panel.show('scaleFadeIn');

Let’s go even further with the next step, animated layouts.

Layout

What if we need our custom animation embedded in a card layout, so cards go out with scaleFadeOut and in with scaleFadeIn? We need to create a layout animation class.

Currently the card layout is the only who actually makes use of animations. The basic idea is to have a class Ext.fx.layout.card.Abstract that listens for the activeitemchange listeners, and once a card changes, the old card is hidden with an out-animation and the new card is shown with an in-animation.

Each layout animation class (Cover, Cube, Fade, Flip, Pop, Reveal…) basically defines the inAnimation and outAnimation, like the class Ext.fx.layout.card.Slide below:

Ext.define('Ext.fx.layout.card.Slide', {
    extend: 'Ext.fx.layout.card.Style',

    alias: 'fx.layout.card.slide',

    config: {
        inAnimation: {
            type: 'slide',
            easing: 'ease-out'
        },
        outAnimation: {
            type: 'slide',
            easing: 'ease-out',
            out: true
        }
    },

    updateReverse: function(reverse) {
        this.getInAnimation().setReverse(reverse);
        this.getOutAnimation().setReverse(reverse);
    }
});

So in order to have our custom scale-fade animation being used as a card layout animation, we need to define a new class:

Ext.define('Ext.fx.layout.card.ScaleFade', {
    extend: 'Ext.fx.layout.card.Style',
    alias: 'fx.layout.card.scalefade',

    config: {
        reverse: null,
        
        inAnimation: {
            type: 'scalefade'
        },
        outAnimation: {
            type: 'scalefadeout'
        }
    }
});

Pretty simple isn’t it? With that we cover the last piece, and we are able to create something like the code below. A container with a card layout using our custom animation for transitions.

{
    xtype: 'container',
    layout: {
        type: 'card',
        animation: 'scalefade'
    },
    items: [{
        html: 'Card 1'
    },{
        html: 'Card 2'
    },{
        html: 'Card 3'
    }]
}

Grab the code!

This wraps up everything about how CSS3 animations works in Sencha Touch. You’re now able to create the coolest animations and tie everything up with Touch components, taking full advantage of the framework.

The topic is a little bit complex, so let me know if you have comments and doubts about it!

Source code available in Sencha Examples Github: animations-advanced-custom

Live example:
*Touch compatibility webkit only