JavaScript-defined animations

We may have to rely on JavaScript sometimes to perform the animation because of browser compatibility issues with CSS animations/transitions. As you know, the moment we introduce ngAnimate as a dependency to our application, the $animate service starts adding/removing the ng-* classes on/off the elements during the course of the animation, but within 0 ms, in case we do not have the CSS animations in place. This gives us a control over how we want to enable the animation, that is, either using CSS or JavaScript.

Let’s create a JavaScript fallback for the example from the Using animate.css section. Add the following to controllers.js:

.animation('.item', function() {
    return {
        enter: function(element, done) {
            element.css({'opacity': 0, 'margin-left': '-230px'});
            element.animate({'opacity': 1, 'margin-left': 0}, 500, done);
        },
        leave: function(element, className, done) {element.animate({'opacity': 0, 'margin-left': -230}, 500, done);
        }
    }
 });

So, what’s happening here?

  1. First of all, we’ve used the animation service (such as controller) in angularjs to enable class-based animation in JavaScript.
  2. The animation name (.item) should begin with a dot, resembling a CSS class definition.
  3. As we are dealing with ngRepeat, we have to take care of the enter and leave events for this particular example. This is applicable to other directives too, such as ngView, ngInclude, ngSwitch, and ngIf. For the remaining directives, such as ngClass, ngShow, form, and ngModel, there are the addClass and removeClass events to consider.
  4. Going ahead with the enter event, we mentioned the start and end states. Initially, we want an element to be out of the viewport, with a negative margin, and hidden with an opacity of 0. Moving towards the destination, we reduced the negative margin to 0 by animating within the viewport and making it visible.
  5. For the leave event, it is reversed.
  6. Note that the done callback is extremely important to be triggered after the animation is over. For CSS animations, angularjs calls it internally. The purpose of the done callback is to remove the ng-* classes on the element upon the completion of the animation. angularjs may not remove the element (mainly in ngRepeat, ngIf, and ngSwitch), assuming that the animation is still in progress if we fail to call the done method, and may not detach those classes.

However, there is one problem: when a CSS animation is supported by the browser, both the CSS and JS animations will collapse. To fix this, we have to know whether the browser has support for transitions/animations. For this, we’ll use Modernizr, a feature detection library for HTML5/CSS3.

We’ll install Modernizr using the following in the command line:

bower install --save modernizr

This will install the latest version of Modernizr in the bower_components/ directory and then we’ll have to insert it in index.html:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <title>Angular Animation</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="bower_components/animate.css/animate.css">
    <link rel="stylesheet/less"  type="text/css" href="css/main.less">
    <script src="bower_components/less/dist/less-1.5.1.js"></script>
    <script src="bower_components/modernizr/modernizr.js"></script>
</head>

Next, we’ll slightly update the defined JavaScript animation to seamlessly fall back. Modernizr has a bunch of tests to check browser support for various features. One of them is cssanimations. Let’s add it, as follows:

.animation('.item', function() {
   if (Modernizr.cssanimations) return angular.noop;
    return {
        enter: function(element, done) {
            element.css({'opacity': 0, 'margin-left': '-230px'});
            element.animate({'opacity': 1, 'margin-left': 0}, 500, done);
        },
        leave: function(element, className, done) {
            element.animate({'opacity': 0, 'margin-left': -230}, 500, done);
        }
   }
});

In this case, we do the following steps:

  1. We first check for the support of cssanimations in a browser using Modernizr.cssanimations—it will return true if there is support; otherwise, it will return false.
  2. The animation service should return an empty object/function if we do not want to overwrite CSS animations. We’ve returned angular.noop to save some keystrokes: a built-in method in angularjs that performs no operation, which is similar to function() { }.
  3. If you use the older version of Internet Explorer, that is, version 8 or 9, then you can catch the JavaScript animation in motion.

Now, our animation will work seamlessly in evergreen browsers that support CSS3 animations as well as old browsers with JS fallback.

Wrapping up

Summary

In this Tutorial, we took a look at the animation functionality in angularjs and how easy it is to simply use CSS transitions/animations in order to make elements dance or fall back to JavaScript animations otherwise. First, we created a simple to-do list application with an animated entry and exit of items using a CSS-based animation. In this application, we also created a custom filter to only show the items that are not marked as completed. We looked at what angular-animate is doing under the hood to facilitate CSS animations. We created motion on the page inside the ng-enter and ng-leave phases. We created more interesting motion paths by making use of the bezier-curve function in CSS. We started using LESS to make our lives easier by introducing variables into CSS and letting us specify values in a central place. We used animate.css as a simple way of importing rather sophisticated animations. We also created a staggering animation to space out multiple elements being animated simultaneously and learned how angularjs manages to do so. Finally, we set up a fallback for CSS transitions/animations in JavaScript. Also, we trained Modernizr to detect the HTML5/CSS3 feature with simple to-use utility functions. In the next Tutorial, we will be covering data-driven charts.