Angular Animation - Under the Hood

In this article, we aim to show you how animation works inside of AngularJS rather than demonstrate the breadth of ngAnimate’s functionality.

The latest version of Angular (1.2.0 and beyond) now includes support for animations in our already feature-packed framework.

With animation built into the framework, it’s simple to integrate fluid animations into our applications in a simple, nonintrusive manner.

The Angular team created the ngAnimate module to give our Angular apps hooks into providing CSS and JavaScript.

There are several ways to make animations in an Angular app:

Installation

Since 1.2.0.rc1, animations have been pulled out of the core of Angular into their own module. In order to include animations in our Angular app, we’ll therefore need to install and reference that module in our app.

We can download it from code.angularjs.org and save it in a place that we can reference from our HTML, like js/vendor/angular-animate.js.

We can also install it using Bower, which will place it in our usual Bower directory. For more information about Bower, see the Bower chapter.

1
$ bower install --save angular-animate

We’ll need to reference this library in our HTML after we reference Angular itself.

1
2
<script src="js/vendor/angular.js"></script> <script src="js/vendor/angular-animate.js"></script>

Lastly, we’ll need to reference the ngAnimate module as a dependency in our app module:

1
angular.module('myApp', ['ngAnimate']);

Now we are ready to take on animations with AngularJS.

How it works

The $animate service itself, by default, applies two CSS classes to the animated element for each animation event (see below).

The $animate service supports several built-in Angular directives that automatically support animation without the need for any extra configuration. It is also flexible enough to enable us to build our own animations for our directives.

All of the pre-existing directives that support animation do so through monitoring events provided on the directives.

For instance, when a new ngView enters and brings new content into the browser, this event is called the enter event for ngView. When ng-hide is ready to show an element, the remove event is fired.

The following is a list of directives and the events that they each fire at different states. We will use these events to define how our animations will work in each state.

Directive Events
ngRepeat enter, leave, move
ngView enter, leave
ngInclude enter, leave
ngSwitch enter, leave
ngIf enter, leave
ngClass add, remove
ngShow add, remove
ngHide add, remove

Using CSS3 Transitions

Using CSS3 Transitions is by far the easiest way to include animations in our app, and it will work for all browsers except IE9 and earlier versions.

Browsers that do not support CSS3 Transitions will gracefully fall back to the non-animated version of the app.

To do any CSS animation, we’ll need to make sure we include the classes we’ll be working with to the DOM element we’re interested in animating.

For instance, in the following demo we’ll animate the following element:

1
<div class="fadein"><h2>Hello</h2></div>

CSS3 Transitions are fully class-based, which means that as long as we have classes that define the animation in our HTML the animation will be animated in the browser.

In order for us to achieve animations with classes, we’ll need to follow the Angular CSS naming conventions to define our CSS transitions.

CSS transitions are effects that let an element gradually change from one style to another style. To define an animation, we must specify the property we want to add an animation to as well as specify the duration of effect.

For instance, this code will add a transition effect on all of the properties on DOM elements with the .fadein class for a two-second duration.

1
2
3
4
5
6
.fadein { transition: 2s linear all; -webkit-transition: 2s linear all; -moz-transition: 2s linear all; -o-transition: 2s linear all; }

With this transition and timing set, we can define properties on different states of the DOM element.

1
2
3
4
.fadein:hover { width: 300px; height: 300px; }

With ngAnimate, Angular starts our directive animations by adding two classes to each animation event: the initial ng-[EVENT] class and, shortly thereafter, the ng-[EVENT]-active class.

The ng-[EVENT] CSS class represents the initial state of the animation, while the ng-[EVENT]-active CSS class is there so we can define the final state of the animation.

To automatically allow the DOM elements from above to transition with Angular animation, we’ll modify the initial .fadein example to include the initial state class:

1
2
3
4
5
6
.fadein.ng-enter { opacity: 0; } .fadein.ng-enter.ng-enter-active { opacity: 1; }

To actually run the animation, we’ll need to include the CSS animation definition. In this definition, we need to include both the duration and the element attributes that we’re going to modify:

1
2
3
4
5
6
.fadein.ng-enter { transition: 1s linear all; -webkit-transition: 1s linear all; -moz-transition: 1s linear all; -o-transition: 1s linear all; }

In this example, we set the element attributes we’re going to modify as all. In doing so, we are telling CSS that we are going to change all of the properties in the new CSS class. If we want to change only a particular property, we can list it above, instead of using all.

Using the ng-if directive, this modification looks like:

1
2
3
4
5
6
7
<div ng-controller="MyController"> <button ng-click="showHello()">Show hello</button> <div ng-if="shouldShowHello" class="fadein"> <h2>Hello</h2> <button ng-click="reset()">Reset</button> </div> </div>

See it

Hello

Using CSS3 Animations

CSS3 animations are more extensive and more complex than CSS3 transitions. They are supported by all major browsers except IE9 and earlier versions of IE. With CSS3 animations, we’ll use the same initial class ng-[EVENT], but we don’t need to define animation states in the ng-[EVENT]-active state, because our CSS rules will handle the rest of the block.

We create the animation in the CSS3 @keyframes rule. Within the CSS element where we define the @keyframes rule, we’ll define the CSS styles that we want to be manipulated.

When we want to animate the DOM element, we’ll use the animation: property to bind the @keyframe CSS property, which applies the animation to the CSS element.

We’ll need to specify both the name of the animation as well as the duration when we do so.

Remember to add the animation duration: If we forget to add the duration of the animation, the duration will default to 0, and the animation will not run.

To create our @keyframes rule, we’ll need to give our keyframe a name and set the time periods for where the properties should be throughout the animation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@keyframes firstAnimation { 0% { color: yellow; } 100% { color: black; } } /* For Chrome and Safari */ @-webkit-keyframes firstAnimation { /* from is equivalent to 0% */ from { color: yellow; } /* from is equivalent to 100% */ to { color: black; } }

Using the keyword from is equivalent to setting the percentage to 0%. Using the keyword to is equivalent to setting the percentage to 100%.

We are not limited to 0% and 100%; we can provide animations in steps, such as at 10%, 15%, etc.

To assign this @keyframe property to the classes we want to animate, we’ll use the animation property, which will apply the animation to the elements targeted by that CSS selector.

1
2
3
4
.fadein:hover { -webkit-animation: 2s firstAnimation; animation: 2s firstAnimation; }

With ngAnimate, we’ll bind the firstAnimation value to any elements that are targeted with the .fadein class. Angular will apply and remove the .ng-enter class for us automatically, so we can simply attach our event to the .fadein.ng-enter class:

1
2
3
4
5
6
.fadein.ng-enter { -moz-animation: 2s firstAnimation; -o-animation: 2s firstAnimation; -webkit-animation: 2s firstAnimation; animation: 2s firstAnimation; }

For the ng-show directive, we can use the ng-add event to show the animation in the DOM.

1
2
3
4
5
6
7
<div ng-init="show=false" ng-controller="HomeCtrl"> <button ng-click="show=!show">Show</button> <div ng-show="show" class="animateMe"> <h2>Show me</h2> </div> </div>

See it

I’m shown

Using JavaScript animations

JavaScript animation is different than the previous two Angular animation methods in that we’ll set properties on the DOM element directly using JavaScript.

JavaScript animation is supported in all major browsers that enable JavaScript, so it’s a good choice if we want to offer animations on browsers that don’t support CSS transitions and animations.

Instead of manipulating our CSS to animate elements, we’ll update our JavaScript to handle running animations for us.

The ngAnimate module adds the .animation method to the module API; this method presents an interface on which we can build our animations.

The animation() method takes two parameters:

This classname will match the class of the element to animate. For our examples thus far, the animation should be named: .fadein.

The animate function is expected to return an object that includes functions for the different events that the directive fires (where it’s used).

See the $animate API docs for detailed documentation on these functions.

1
2
3
4
5
6
7
8
9
10
11
12
angular.module('myApp', ['ngAnimate']) .animation('.fadein', function() { return { enter: function(element, done) { // Run animation return function(cancelled) { // Cancel animation } } } });

The $angular service will call these functions for the element specified. Inside these functions, we are free to do what we will with the element. The only requirement is that we call the callback function done() when we are done with the animation.

Inside these functions, we can return an end function that will be called when the animation is complete OR the animation has been canceled.

When the animation is triggered, $animate will look for the matching animation function for the event. If it finds a function that matches the event, it will execute it.

Example time

We’re going to create our animation using the JavaScript API:

See it

This example is set up using this HTML:

1
2
3
4
<div ng-controller="DemoCtrl"> <div square-scroller> </div> </div>

We’re not declaring any classes, only setting up a directive. The directive (shortened so we can keep this section brief) sets up a template that includes the images and sets the animation class that we’ll define.

Lastly, it stores the current offsetTop for the element in a data attribute (i.e., data-offsetTop=“” in the HTML):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.directive('squareScroller', function($animate) { var clicked = false; return { scope: {}, template: '<div ng-init="runAnimation=false">\ <div ng-hide="!runAnimation" class="square-scroll">\ <img src="/images/animation/one.png" />\ <img src="/images/animation/two.png" />\ <img src="/images/animation/three.png" />\ </div>\ </div>', link: function(scope, ele, attrs, ctrl) { angular.forEach(ele.find("img"), function(e, idx) { var offsetTop = $(e).offset().top; $(e).attr('data-offsetTop', offsetTop); }); } } });

This directive doesn’t do any of the animation on its own; rather, it only sets up the template. The real magic of animations happen with the .square-scroll animation.

Using the .animation API, we’ll create our animation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.animation('.square-scroll', function() { return { removeClass: function(ele, className, done) { if (className === 'ng-hide') { // We're unhiding the element // i.e. showing the element } else { done(); } }, addClass: function(ele, className, done) { if (className === 'ng-hide') { // We're hiding the element } else { done(); } } } });

This function sets up the .square-scroll CSS to handle two animation events: the addClass and removeClass events. We’re using these events because we’re using the ng-hide directive to show or hide our elements, which effectively just adds the ng-hide class on the elements.

When the ng-hide directive is ready to show an element, it will fire the removeClass function (it’s removing the ng-hide CSS class). When it’s going to hide the element, it will add the ng-hide CSS class to the element.

Our .animation handles this animation above by checking for the className that is being added or removed and making sure we only add animations if we’re showing or hiding the element.

The full source of the directive can be found here.

Get the weekly email all focused on AngularJS. Sign up below to receive the weekly email and exclusive content.
We will never send you spam and it's a cinch to unsubscribe.

Download a free sample of the ng-book: The Complete Book on AngularJS

ng-book: The Complete Book on AngularJS is the canonical AngularJS book available today.

It's free, so just enter your email address and the PDF will be sent directly to your inbox. Mailchimp can take up to an hour to deliver the free sample chapter, but if you don't receive it within the hour, send us an email and we'll manually send them to you!

We'll send you updates about the book, when it updates and other free content.

We will never send you spam and it's a cinch to unsubscribe.

Comments

comments powered by Disqus