In this Lesson, we will cover the following recipes:
slideUp()
and slideDown()
methodsngIf
ngView
ngRepeat
addClass
animations with ngShow
removeClass
animations with ngClass
AngularJS incorporates its animation infrastructure as a separate module, ngAnimate
. With this, you are able to tackle animating your application in several different ways, which are as follows:
Using any one of these three, you are able to fully animate your application in an extremely clean and modular fashion. In many cases, you will find that it is possible to add robust animations to your existing application using only the AngularJS class event progression and CSS definitions—no extra HTML or JS code is needed.
This Lesson assumes that you are at least broadly familiar with the major topics involved in browser animations. We will focus more on how to integrate these animations into an AngularJS application without having to rely on jQuery or other animation libraries. As you will see in this Lesson, there are a multitude of reasons why utilizing AngularJS/CSS animations is preferred to their respective counterparts in libraries such as jQuery.
For the sake of brevity, the recipes in this Lesson will not include any vendor prefixes in the CSS class or animation definitions. Production applications should obviously include them for cross-browser compatibility, but in the context of this Lesson, they are merely a distraction as AngularJS is unconcerned with the content of CSS definitions.
The ngAnimate
module comes separately packaged in angular-animate.js
. This file must be included alongside angular.js
for the recipes in this Lesson to work.
AngularJS animations work by integrating CSS animations into a directive class-based finite state machine. In other words, elements in AngularJS that serve to manipulate the DOM have defined class states that can be used to take full advantage of CSS animations, and the system moves between these states on well-defined events. This recipe will demonstrate how to make use of the directive finite state machine in order to create a simple fade in/out animation.
A finite state machine (FSM) is a computational system model defined by the states and transition conditions between them. The system can only exist in one state at any given time, and the system changes state when triggered by certain events. In the context of AngularJS animations, states are represented by the presence of CSS classes associated with the progress of a certain animation, and the events that trigger the state transformations are controlled by data binding and the directives controlling the classes.
As of AngularJS 1.2, animation comes as a completely separate module in AngularJS—ngAnimate
. Your initial files should appear as follows:
(style.css) .animated-container { padding: 20px; border: 5px solid black; } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <label> <button ng-click="boxHidden=!boxHidden"> Toggle Visibility </button> </label> <div class="animated-container" ng-hide="boxHidden"> Awesome text! </div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.boxHidden = true; });
You can see that the given code simply provides a button that instantly toggles the visibility of the styled <div>
element.
There are several ways to accomplish a fade in/out animation, but the simplest is to use CSS transitions as they integrate very nicely into the AngularJS animation class state machine.
The animation CSS classes need to cover both cases, where the element is hidden and needs to fade in, and where the element is shown and needs to fade out. As is the case with CSS transitions, you need to define the initial state, the final state, and the transition parameters. This can be done as follows:
(style.css) .animated-container { padding: 20px; border: 5px solid black; } .animated-container.ng-hide-add, .animated-container.ng-hide-remove { transition: all linear 1s; } .animated-container.ng-hide-remove, .animated-container.ng-hide-add.ng-hide-add-active { opacity: 0; } .animated-container.ng-hide-add, .animated-container.ng-hide-remove.ng-hide-remove-active { opacity: 1; }
JSFiddle: http://jsfiddle.net/msfrisbie/fqxwvyvj/
These CSS classes cover the bi-directional transition to fade between opacity: 0
and opacity: 1
in 1 second. Clicking on the <button>
element to toggle the visibility will work to trigger the fade in and fade out of the styled <div>
element.
Since CSS transitions are triggered by the change of relevant CSS classes, using the AngularJS class state machine allows you to animate when a directive manipulates the DOM. The show/hide state machine is cyclical and operates as shown in the following table (this is a simplified version of the full ng-show
/ng-hide
state machine, which is provided in detail in the Creating addClass animations with ngShow recipe):
Event |
Directive state |
Styled element classes |
Element state |
---|---|---|---|
Initial state |
|
|
|
|
|
|
|
Time quanta elapses |
|
|
The animation is triggered; transition to |
Animation completes |
|
|
|
|
|
|
|
Time quanta elapses |
|
|
The animation is triggered; transition to |
Animation completes |
|
|
|
You can now see how the CSS classes utilize the animation class state machine to trigger the animation. When the directive state changes (in this case, the Boolean is negated), AngularJS applies sequential CSS classes to the element, intending them to be used as anchors for a CSS animation. Here, Time quanta elapses refers to the separate addition of ng-hide-add
or ng-hide-remove
followed by the ng-hide-add-active
or ng-hide-remove-active
classes. These classes are added sequentially and separately (this appears to be instantaneous, you will be unable to see the separation when watching the classes in a browser inspector), but the nature of the offset addition causes the CSS transition to be triggered properly.
In the case of moving from hidden to visible, the CSS styling defines a transition between the .animated-container.ng-hide-add
selector and the .animated-container.ng-hide-add.ng-hide-add-active
selector, with the transition definition attached under the .animated-container.ng-hide-remove
selector.
In the case of moving from visible to hidden, the styling defines the opposite transition between the .animated-container.ng-hide-add
selector and the .animated-container.ng-hide-add.ng-hide-add-active
selector, with the transition definition attached under the .animated-container.ng-hide-add
selector.
As the class state machine is controlled entirely by the ng-hide
directive, if you want to invert the animation (initially start as shown and then make the transition to hidden), all that is needed is the use of ng-show
on the HTML element instead of ng-hide
. These opposing directives will implement the class state machine appropriately for their definition, but will always use the ng-hide
class as the default reference. In other words, using the ng-show
directive will not utilize ng-show-add
or ng-show-remove
or anything of the sort; it will still be ng-hide
, ng-hide-add
or ng-hide-remove
, and ng-hide-add-active
or ng-hide-remove-active
.
Since the animation starts as hidden, and you are loading the JS files at the bottom of the body, this is the perfect opportunity to utilize ng-cloak
in order to prevent the styled div
element from flashing before compilation. Modify your CSS and HTML as follows:
(style.css)
[ng:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
(index.html)
...
<div class="animated-container" ng-show="boxHidden" ng-cloak>
Awesome text!
</div>
jQuery provides a very useful pair of animation methods, slideUp()
and slideDown()
, which use JavaScript in order to accomplish the desired results. With the animation hooks provided for you by AngularJS, these animations can be accomplished with CSS.
Suppose that you want to slide a <div>
element up and down in the following setup:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="displayToggle=!displayToggle"> Toggle Visibility </button> <div>Slide me up and down!</div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.displayToggle = true; });
A sliding animation requires truncation of the overflowing element and a transition involving the height of the element. The following implementation utilizes ng-class
:
(style.css) .container { overflow: hidden; } .slide-tile { transition: all 0.5s ease-in-out; width: 300px; line-height: 300px; text-align: center; border: 1px solid black; transform: translateY(0); } .slide-up { transform: translateY(-100%); } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="displayToggle=!displayToggle"> Toggle Visibility </button> <div class="container"> <div class="slide-tile" ng-class="{'slide-up': !displayToggle}"> Slide me up and down! </div> </div> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/eqcs1dzr/
A slightly more lightweight implementation is to tie the class definitions into the ng-show
state machine:
(style.css) .container { overflow: hidden; } .slide-tile { transition: all 0.5s ease-in-out; width: 300px; line-height: 300px; text-align: center; border: 1px solid black; transform: translateY(0); } .slide-tile.ng-hide { transform: translateY(-100%); } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="displayToggle=!displayToggle"> Toggle Visibility </button> <div class="container"> <div class="slide-tile" ng-show="displayToggle"> Slide me up and down! </div> </div> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/bx01muha/
CSS transitions afford the convenience of a bi-directional animation as long as the endpoints and transitions are defined. For both of these implementations, the translateY
CSS property is used to implement the sliding, and the hidden state (slide up for the ng-class
implementation, and ng-hide
for the ng-show
implementation) is used as the concealed transition state endpoint.
AngularJS provides hooks to define a custom animation when a directive fires an enter
event. The following directives will generate enter events:
ngIf
: This fires the enter
event just after the ngIf
contents change, and a new DOM element is created and injected into the ngIf
containerngInclude
: This fires the enter
event when new content needs to be brought into the browserngRepeat
: This fires the enter
event when a new item is added to the list or when an item is revealed after a filterngSwitch
: This fires the enter
event after the ngSwitch
contents change, and the matched child element is placed inside the containerngView
: This fires the enter
event when new content needs to be brought into the browserngMessage
: This fires the enter
event when an inner message is attachedSuppose that you want to attach a fade-in animation to a piece of the DOM that has a ng-if
directive attached to it. When the ng-if
expression evaluates to true
, the enter
animation will trigger, as the template is brought into the page.
The initial setup, before animation is implemented, can be structured as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="visible=!visible">Toggle</button> <span class="target" ng-if="visible">Bring me in!</span> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.visible = true; });
The example in this recipe only uses ngIf
, but it could have just as easily been performed with ngInclude
, ngRepeat
, ngSwitch
, or ngView
. All of the enter
events fired for these directives involve content being introduced to the DOM in some way, so the animation hooks and procedures surrounding the animation definition can be handled in a more or less identical fashion.
When the button is clicked, this code instantaneously brings the <div>
element with a ngIf
expression attached to it into view as soon as the expression evaluates to true
. However, with the inclusion of the ngAnimate
module, AngularJS will add in animation hooks, upon which you can define an animation when the <div>
element enters the page.
An animation can be defined by a CSS transition, CSS animation, or by JavaScript. The animation definition can be constructed in different ways. CSS transitions and CSS animations will use the ng-enter
CSS class hooks to define the animation, whereas JavaScript animations will use the ngAnimate
module's enter()
method.
To animate with transitions, only the beginning and end state class styles need to be defined. This is shown here:
(style.css) .target.ng-enter { transition: all linear 1s; opacity: 0; } .target.ng-enter.ng-enter-active { opacity: 1; }
JSFiddle: http://jsfiddle.net/msfrisbie/zhuffnfj/
Similar to CSS3 transition, it is relatively simple to accomplish the same animation with CSS keyframes. Since the animation is defined entirely within the keyframes, only a single class reference is needed in order to trigger the animation. This can be done as follows:
(style.css) .target.ng-enter { animation: 1s target-enter; } @keyframes target-enter { from { opacity: 0; } to { opacity: 1; } }
JSFiddle: http://jsfiddle.net/msfrisbie/rp4mjgkL/
Animating with JavaScript requires that you manually add and remove the relevant CSS classes, as well as explicitly call the animations. Since AngularJS and jqLite objects don't have an animation method, you will need to use the jQuery object's animate()
method:
(app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function ($scope) { $scope.visible = false; }) .animation('.target', function () { return { enter: function (element, done) { $(element) .css({ 'opacity': 0 }); $(element) .animate({ 'opacity': 1 }, 1000, done); } }; });
JSFiddle: http://jsfiddle.net/msfrisbie/2jt853no/
The enter
animation behaves as a state machine. It cannot assume that either CSS transitions/animations or JavaScript animations are defined upon the <div>
DOM element, and it must be able to apply all of them without creating conflicts. As a result, AngularJS will trigger the JavaScript animations and immediately begin the progression of the animation class sequence, which will trigger any CSS transitions/animations that might be defined upon them. In this way, both JavaScript and CSS animations can be used on the same DOM element simultaneously.
AngularJS uses a standard class naming convention for different states, which allows you to uniquely define each set of animations for the component being animated. The following set of tables define how the enter
animation state machine operates.
The initial state of the animation components is defined as follows:
|
[<span class="target" ng-if="visible"> Bring me in! </span>, <!-- end ngIf: visible -->] |
|
[<div> ... </div>] |
|
[<!-- ngIf: visible -->] |
The following table represents a full enter animation transition:
Event |
DOM |
---|---|
The |
<div> <!-- ngIf: visible --> </div> |
The element is inserted into |
<div> <!-- ngIf: visible --> <span class="target" ng-if="visible"> Bring me in! </span> <!-- end ngIf: visible --> </div> |
The |
<div>
<!-- ngIf: visible -->
<span class="target ng-animate"
ng-if="visible">
Bring me in!
</span>
<!-- end ngIf: visible -->
</div> |
The |
No change in DOM |
The |
<div>
<!-- ngIf: visible -->
<span class="target ng-animate ng-enter"
ng-if="visible">
Bring me in!
</span>
<!-- end ngIf: visible -->
</div> |
The |
No change in DOM |
The |
No change in DOM |
The |
No change in DOM |
The |
No change in DOM |
The |
<div>
<!-- ngIf: visible -->
<span class="target ng-animate ng-enter ng-enter-active"
ng-if="visible">
Bring me in!
</span>
<!-- end ngIf: visible -->
</div>
|
No change in DOM | |
Animation completes; animation classes are stripped from the element |
<div>
<!-- ngIf: visible -->
<span class="target"
ng-if="visible">
Bring me in!
</span>
<!-- end ngIf: visible -->
</div>
|
The |
No change in DOM |
AngularJS provides hooks used to define a custom animation when a directive fires a leave
event. The following directives will generate leave
events:
ngIf
: This fires the leave
event just before the ngIf
contents are removed from the DOMngInclude
: This fires the leave
event when the existing included content needs to be animated awayngRepeat
: This fires the leave
event when an item is removed from the list or when an item is filtered outngSwitch
: This fires the leave
event just after the ngSwitch
contents change and just before the former contents are removed from the DOMngView
: This fires the leave
event when the existing ngView
content needs to be animated awayngMessage
: This fires the leave
event when an inner message is removedSuppose that you want to attach a slide-in or slide-out animation to a piece of the DOM that exists inside the ng-view
directive. Route changes that cause the content of ng-view
to be altered will trigger an enter
animation for the content about to be brought into the page, as well as trigger a leave
animation for the content about to leave the page.
The initial setup, before animation is implemented, can be structured as follows:
(style.css) .link-container { position: absolute; top: 320px; } .animate-container { position: absolute; } .animate-container div { width: 300px; text-align: center; line-height: 300px; border: 1px solid black; } (index.html) <div ng-app="myApp"> <ng-view class="animate-container"></ng-view> <div class="link-container"> <a href="#/foo">Foo</a> <a href="#/bar">Bar</a> </div> <script type="text/ng-template" id="foo.html"> <div> <span>Foo</span> </div> </script> <script type="text/ng-template" id="bar.html"> <div> <span>Bar</span> </div> </script> </div> (app.js) angular.module('myApp', ['ngAnimate', 'ngRoute']) .config(function ($routeProvider) { $routeProvider .when('/bar', { templateUrl: 'bar.html' }) .otherwise({ templateUrl: 'foo.html' }); });
The example in this recipe only uses ngView
, but it could have just as easily been performed with ngInclude
, ngRepeat
, ngSwitch
, or ngIf
. All the leave
events fired for these directives involve content being removed from the DOM in some way, so the animation's hooks and procedures surrounding the animation definition can be handled in a more or less identical fashion. However, not all of these directives trigger enter
and leave
events concurrently.
When the route changes, AngularJS instantaneously injects the appropriate template into the ng-view
directive. However, with the inclusion of the ngAnimate
module, AngularJS will add in animation hooks, upon which you can define animations for how the templates will enter and leave the page.
An animation can be defined by a CSS transition, CSS animation, or by JavaScript. The animation definition can be constructed in different ways. CSS transitions and CSS animations will use the ng-leave
CSS class hooks to define the animation, whereas JavaScript animations will use the ngAnimate
directive's leave()
method.
It is important to note here that ng-view
triggers the leave
and enter
animations simultaneously. Therefore, your animation definitions must take this into account in order to prevent animation conflicts.
To animate with transitions, only the beginning and end state class styles need to be defined. Remember that the enter
and leave
animations begin at the same instant, so you must either define an animation that gracefully accounts for any overlap that might occur, or introduce a delay in animations in order to serialize them.
CSS transitions accept a transition-delay value, so serializing the animations is the easiest way to accomplish the desired animation here. Adding the following to the style sheet is all that is needed in order to define the slide-in or slide-out animation:
(style.css) .animate-container.ng-enter { /* final value is the transition delay */ transition: all 0.5s 0.5s; } .animate-container.ng-leave { transition: all 0.5s; } .animate-container.ng-enter, .animate-container.ng-leave.ng-leave-active { top: -300px; } .animate-container.ng-leave, .animate-container.ng-enter.ng-enter-active { top: 0px; }
JSFiddle: http://jsfiddle.net/msfrisbie/y9de80ga/
Building this animation with CSS keyframes is also easy to accomplish. Keyframe percentages allow you to effectively delay the enter animation by a set length of time until the leave animation finishes. This can be done as follows:
(style.css) .animate-container.ng-enter { animation: 1s view-enter; } .animate-container.ng-leave { animation: 0.5s view-leave; } @keyframes view-enter { 0%, 50% { top: -300px; } 100% { top: 0px; } } @keyframes view-leave { 0% { top: 0px; } 100% { top: -300px; } }
JSFiddle: http://jsfiddle.net/msfrisbie/penaakxy/
Animating with JavaScript requires that you manually add and remove the relevant CSS classes, as well as explicitly call the animations. Since AngularJS and jqLite objects don't have an animation method, you will need to use the jQuery object's animate()
method. The delay between the serialized animations can be accomplished with the jQuery delay()
method. The animation can be defined as follows:
(app.js) angular.module('myApp', ['ngAnimate', 'ngRoute']) .config(function ($routeProvider) { $routeProvider .when('/bar', { templateUrl: 'bar.html' }) .otherwise({ templateUrl: 'foo.html' }); }) .animation('.animate-container', function() { return { enter: function(element, done) { $(element) .css({ 'top': '-300px' }); $(element) .delay(500) .animate({ 'top': '0px' }, 500, done); }, leave: function(element, done) { $(element) .css({ 'top': '0px' }); $(element) .animate({ 'top': '-300px' }, 500, done); } }; });
JSFiddle: http://jsfiddle.net/msfrisbie/b4L35nrt/
The leave
animation state machine has a good deal of parity with the enter
animation. State machine class progressions work in a very similar way; sequentially adding the beginning and final animation hook classes in order to match the element coming in and out of existence. AngularJS uses the same standard class naming convention used by the enter
animation for the different animation states. The following set of tables define how the leave
animation state machine operates.
The initial state of the animation components is defined as follows:
|
[<ng-view class="animate-container"> <div> <span>Bar</span> </div> </ng-view>]
|
The following table represents a full leave animation transition:
Event |
DOM |
---|---|
The |
<ng-view class="animate-container"> <div> <span>Bar</span> </div> </ng-view>
|
The |
<ng-view class="animate-container ng-animate">
<div>
<span>Bar</span>
</div>
</ng-view>
|
The |
No change in DOM |
The |
<ng-view class="animate-container ng-animate ng-leave">
<div>
<span>Bar</span>
</div>
</ng-view>
|
The |
No change in DOM |
The |
No change in DOM |
The |
No change in DOM |
The |
No change in DOM |
The |
<ng-view class="animate-container ng-animate ng-leave ng-leave-active">
<div>
<span>Bar</span>
</div>
</ng-view>
|
The |
No change in DOM |
The animation is complete; animation classes are stripped from the element |
<ng-view class="animate-container"> <div> <span>Bar</span> </div> </ng-view>
|
The element is removed from DOM |
<ng-view class="animate-container"> </ng-view>
|
The |
No change in DOM |
AngularJS provides hooks to define a custom animation when a directive fires a move
event. The only AngularJS directive that fires a move
event by default is ngRepeat
; it fires a move
event when an adjacent item is filtered out causing a reorder or when the item contents are reordered.
Suppose that you want to attach a slide-in or slide-out animation to a piece of the DOM that exists inside the ng-view
directive. Route changes that cause the content of ng-view
to be altered will trigger an enter
animation for the content about to be brought into the page, as well as trigger a leave
animation for the content about to leave the page.
Suppose that you want to animate individual pieces of a list when they are initially added, moved, or removed. Additions and removals should slide in and out from the left-hand side, and move
events should slide up and down.
The initial setup, before animation is implemented, can be structured as follows:
(style.css) .animate-container { position: relative; margin-bottom: -1px; width: 300px; text-align: center; border: 1px solid black; line-height: 40px; } .repeat-container { position: absolute; } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <div style="repeat-container"> <input ng-model="search.val" /> <button ng-click="shuffle()">Shuffle</button> <div ng-repeat="el in arr | filter:search.val" class="animate-container"> <span>{{ el }}</span> </div> </div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.arr = [10,15,25,40,45]; // implementation of Knuth in-place shuffle function knuthShuffle(a) { for(var i = a.length, j, k; i; j = Math.floor(Math.random() * i), k = a[--i], a[i] = a[j], a[j] = k); return a; } $scope.shuffle = function() { $scope.arr = knuthShuffle($scope.arr); }; });
When the order of the displayed iterable collection changes, AngularJS injects the appropriate template into the corresponding location in the list, and sibling elements whose indices have changed will instantaneously shift. However, with the inclusion of the ngAnimate
module, AngularJS will add in animation hooks, upon which you can define animations for how the templates will move within the list.
The animation can be defined by a CSS transition, CSS animation, or by JavaScript. The animation definition can be constructed in different ways. CSS transitions and CSS animations will use the ng-move
CSS class hooks in order to define the animation, whereas JavaScript animations will use the ngAnimate
module's move()
method.
It is important to note here that ng-repeat
triggers enter
, leave
, and move
animations simultaneously. Therefore, your animation definitions must take this into account to prevent animation conflicts.
To animate with transitions, you can utilize the animation hook class states to define the set of endpoints for each type of animation. Animations on each individual element in the collection will begin simultaneously, so you must define animations that gracefully account for any overlap that might occur.
Adding the following to the style sheet is all that is needed in order to define the slide-in or slide-out animation for the enter and leave events and a fade in for the move event:
(style.css) .animate-container.ng-move { transition: all 1s; opacity: 0; max-height: 0; } .animate-container.ng-move-active { opacity: 1; max-height: 40px; } .animate-container.ng-enter { transition: left 0.5s, max-height 1s; left: -300px; max-height: 0; } .animate-container.ng-enter-active { left: 0px; max-height: 40px; } .animate-container.ng-leave { transition: left 0.5s, max-height 1s; left: 0px; max-height: 40px; } .animate-container.ng-leave-active { left: -300px; max-height: 0; }
JSFiddle: http://jsfiddle.net/msfrisbie/f4puyv58/
Building this animation with CSS keyframes allows you to have the advantage of being able to explicitly define the offset between animation segments, which allows you a cleaner enter/leave animation without tiles sweeping over each other. The enter and leave animations can take advantage of this by animating to full height before sliding into view. Add the following to the style sheet in order to define the desired animations:
(style.css) .animate-container.ng-enter { animation: 0.5s item-enter; } .animate-container.ng-leave { animation: 0.5s item-leave; } .animate-container.ng-move { animation: 0.5s item-move; } @keyframes item-enter { 0% { max-height: 0; left: -300px; } 50% { max-height: 40px; left: -300px; } 100% { max-height: 40px; left: 0px; } } @keyframes item-leave { 0% { left: 0px; max-height: 40px; } 50% { left: -300px; max-height: 40px; } 100% { left: -300px; max-height: 0; } } @keyframes item-move { 0% { opacity: 0; max-height: 0px; } 100% { opacity: 1; max-height: 40px; } }
JSFiddle: http://jsfiddle.net/msfrisbie/1632jm5g/
JavaScript animations are also relatively easy to define here, even though the desired effect has both serialized and parallel animation effects. This can be done as follows:
(app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { ... }) .animation('.animate-container', function() { return { enter: function(element, done) { $(element) .css({ 'left': '-300px', 'max-height': '0' }); $(element) .animate({ 'max-height': '40px' }, 250) .animate({ 'left': '0px' }, 250, done); }, leave: function(element, done) { $(element) .css({ 'left': '0px', 'max-height': '40px' }); $(element) .animate({ 'left': '-300px' }, 250) .animate({ 'max-height': '0' }, 250, done); }, move: function(element, done) { $(element) .css({ 'opacity': '0', 'max-height': '0' }); $(element) .animate({ 'opacity': '1', 'max-height': '40px' }, 500, done); } }; });
JSFiddle: http://jsfiddle.net/msfrisbie/rjaq5tqc/
The move
animation state machine is very similar to the enter
animation. State machine class progressions sequentially add the beginning and final animation hook classes in order to match the element that is being reintroduced into the list at its new index. AngularJS uses the same standard class naming convention used by the enter animation for different animation states.
For the purpose of simplification, the following modifications and assumptions affect the content of the following state machine:
ng-repeat
directive is assumed to be passed an array of [1,2]. The move
event is triggered by the array's order being reversed to [2,1].ng-repeat
filter has been removed; a search filter cannot fire move events.ng-scope
and ng-binding
directive classes have been removed from where they would normally occur, as they do not affect the state machine.The following set of tables define how the move
animation state machine operates.
The initial state of the animation components is defined as follows:
|
[<div ng-repeat="el in arr" class="animate-container"> <span>1</span> </div>, <!-- end ngRepeat: el in arr -->]
|
|
|
|
[<!-- ngRepeat: el in arr -->]
|
The following table represents a full move animation transition:
The move
animation's name can be a bit confusing as move implies a starting and ending location. A better way to think of it is as a secondary entrance animation used in order to demonstrate when new content is not being added to the list. You will notice that the move
animation is triggered simultaneously for all the elements whose relative order in the list has changed, and that the animation triggers when it is in its new position.
Also note that even though the index of both elements changed, only one move
animation was triggered. This is due to the way the movement within an enumerable collection is defined. AngularJS preserves the old ordering of the collection and compares its values in order to the entire new ordering, and all mismatches will fire move
events. For example, if the old order is 1, 2, 3, 4, 5 and the new order is 5, 4, 2, 1, 3, then the comparison strategy works as follows:
Comparison |
Evaluation |
---|---|
old[0] == new[0]
|
False, fire the |
old[0] == new[1]
|
False, fire the |
old[0] == new[2]
|
False, fire the |
old[0] == new[3]
|
True, increment the old order comparison index until an element, which was not yet seen, is reached (2 was already seen in the new order; skip to 3) |
old[2] == new[4]
|
True |
AngularJS provides hooks used to define a custom animation when a directive fires an addClass
event. The following directives will generate addClass
events:
ngShow
: This fires the addClass
event after the ngShow
expression evaluates to a truthy value, and just before the contents are set to visiblengHide
: This fires the addClass
event after the ngHide
expression evaluates to a non-truthy value, and just before the contents are set to visiblengClass
: This fires the addClass
event just before the class is applied to the elementngForm
: This fires the addClass
event to add validation classesngModel
: This fires the addClass
event to add validation classesngMessages
: This is fired to add the ng-active
class when one or more messages are visible, or to add the ng-inactive
class when there are no messagesSuppose that you want to attach a fade-out animation to a piece of the DOM that has an ng-show
directive. Remember that ng-show
does not add or remove anything from the DOM; it merely toggles the CSS display property to set the visibility.
The initial setup, before animation is implemented, can be structured as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="displayToggle=!displayToggle"> Toggle Visibility </button> <div class="animate-container" ng-show="displayToggle"> Fade me out! </div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl',function($scope) { $scope.displayToggle = true; });
When the ng-show
expression evaluates to false
, the DOM element is immediately hidden. However, with the inclusion of the ngAnimate
module, AngularJS will add in animation hooks, upon which you can define animations for how the element will be removed from the page.
The animation can be defined by a CSS transition, CSS animation, or by JavaScript. The animation definition can be constructed in different ways. CSS transitions and CSS animations will use the addClass
CSS class hooks to define the animation, whereas JavaScript animations will use the ngAnimate
directive's addClass()
method.
Animating a fade-in effect with CSS transitions simply requires attaching opposite opacity values when the ng-hide
class is added. Remember that ng-show
and ng-hide
are merely toggling the presence of this ng-hide
class through the use of the addClass
and removeClass
animation events. This can be done as follows:
(style.css) .animate-container.ng-hide-add { transition: all linear 1s; opacity: 1; } .animate-container.ng-hide-add.ng-hide-add-active { opacity: 0; }
JSFiddle: http://jsfiddle.net/msfrisbie/bewso5sd/
Animating with a CSS animation is just as simple as CSS transitions, as follows:
(style.css) .animate-container.ng-hide-add { animation: 1s fade-out; } @keyframes fade-out { 0% { opacity: 1; } 100% { opacity: 0; } }
JSFiddle: http://jsfiddle.net/msfrisbie/aez97r46/
Animating with JavaScript requires that you manually add and remove the relevant CSS classes, as well as explicitly call the animations. Since AngularJS and jqLite objects don't have an animation method, you will need to use the jQuery object's animate()
method. This can be done as follows:
(app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.displayToggle = true; }) .animation('.animate-container', function() { return { addClass: function(element, className, done) { if (className==='ng-hide') { $(element) .removeClass('ng-hide') .css('opacity', 1) .animate( {'opacity': 0}, 1000, function() { $(element) .addClass('ng-hide') .css('opacity', 1); done(); } ); } else { done(); } } }; });
JSFiddle: http://jsfiddle.net/msfrisbie/4taoda1e/
Note that here, the opacity
value is used for the animation, but is not the active class that hides the element. After its use in the animation, it must be reset to 1 in order to not interfere with the subsequent display toggling.
Independent of what is defined in the actual class that is being added, ngAnimate
provides animation hooks for the class that is being added to define animations. In the context of the ng-show
directive, the ng-hide
CSS class is defined implicitly within AngularJS, but the animation hooks are completely decoupled from the original class in order to provide a fresh animation definition interface. The following set of tables defines how the addClass
animation state machine operates.
The initial state of the animation components is defined as follows:
|
<div class="animate-container" ng-show="displayToggle"> Fade me out! </div>
|
|
'ng-hide'
|
The following table represents a full addClass
animation transition:
AngularJS provides hooks that can be used to define a custom animation when a directive fires a removeClass
event. The following directives will generate removeClass
events:
ngShow
: This fires the removeClass
event after the ngShow
expression evaluates to a non-truthy value, and just before the contents are set to hiddenngHide
: This fires the removeClass
event after the ngHide
expression evaluates to a truthy value, and just before the contents are set to hiddenngClass
: This fires the removeClass
event just before the class is removed from the elementngForm
: This fires the removeClass
event to remove validation classesngModel
: This fires the removeClass
event to remove validation classesngMessages
: This fires the removeClass
event to remove the ng-active
class when there are no messages, or to remove the ng-inactive
class when one or more messages are visibleSuppose that you want to have a div
element slide out of the view when a class is removed. Remember that ng-class
does not add or remove any elements from the DOM; it merely adds or removes the classes defined within the directive expression.
The initial setup, before animation is implemented, can be structured as follows:
(style.css) .container { background-color: black; width: 200px; height: 200px; overflow: hidden; } .prompt { position: absolute; margin: 10px; font-family: courier; color: lime; } .cover { position: relative; width: 200px; height: 200px; left: 200px; background-color: black; } .blackout { left: 0; } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="displayToggle=!displayToggle"> Toggle Visibility </button> <div class="container"> <span class="prompt">Wake up, Neo...</span> <div class="cover" ng-class="{blackout: displayToggle}"> </div> </div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.displayToggle = true; });
When the ng-class
value for blackout
evaluates to false
, it will immediately be stripped out. However, with the inclusion of the ngAnimate
module, AngularJS will add in animation hooks, upon which you can define animations for how the class will be removed.
The animation can be defined by a CSS transition, CSS animation, or by JavaScript. The animation definition can be constructed in different ways. CSS transitions and CSS animations will use the removeClass
CSS class hooks to define the animation, whereas JavaScript animations will use the ngAnimate
directive's removeClass()
method.
Animating a slide-out effect with CSS transitions simply requires a transition that defines the left positioning distance. Remember that ng-class
is merely toggling the presence of the blackout
class through the use of the addClass
and removeClass
animation events. This can be done as follows:
(style.css) .blackout-remove { left: 0; } .blackout-remove { transition: all 3s; } .blackout-remove-active { left: 200px; }
JSFiddle: http://jsfiddle.net/msfrisbie/L6u4nzv7/
Animating with a CSS animation is just as simple as CSS transitions, as follows:
(style.css) .blackout-remove { animation: 1s slide-out; } @keyframes slide-out { 0% { left: 0; } 100% { left: 200px; } }
JSFiddle: http://jsfiddle.net/msfrisbie/oq5ha3zq/
Animating with JavaScript requires that you manually add and remove the relevant CSS classes, as well as explicitly call the animations. Since AngularJS and jqLite objects don't have an animation method, you will need to use the jQuery object's animate()
method. This can be done as follows:
(app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.displayToggle = true; }) .animation('.blackout', function() { return { removeClass: function(element, className, done){ if (className==='blackout') { $(element) .removeClass('blackout') .css('left', 0) .animate( {'left': '200px'}, 3000, function() { $(element).css('left',''); done(); } ); } else { done(); } } }; });
JSFiddle: http://jsfiddle.net/msfrisbie/4dnokg2o/
The ngAnimate
directive provides animation hooks for the class that is being removed in order to define animations independent of the actual class. In the context of this ng-class
directive implementation, the blackout
CSS class is defined explicitly, and the animation hooks build on top of this class name. The following set of tables defines how the removeClass
animation state machine operates.
The animation components are defined as follows:
|
<div class="cover blackout" ng-class="{blackout: displayToggle}"> </div>
|
|
'blackout'
|
The following table represents a full removeClass
animation transition:
In the previous recipes we looked at creating different types of animation in varying scenarios, let's try to create animation on the page scroll. You might have seen this a number of times, whenever you scroll down an application, elements should animate. The duration for animation should be 2 seconds.
This is very similar to what we have done in one of the preceding sections. Here, we will look at the page load factor. This is a bit tricky; but once this challenge is done, you will feel more confident about your animation skills.
Let us know your output @PacktPub
!
AngularJS incorporates native support for staggering animations that happen as a batch. This will almost exclusively occur in the context of ng-repeat
.
Suppose that you have an animated ng-repeat
implementation, as follows:
(style.css) .container { line-height: 30px; } .container.ng-enter, .container.ng-leave, .container.ng-move { transition: all linear 0.2s; } .container.ng-enter, .container.ng-leave.ng-leave-active, .container.ng-move { opacity: 0; max-height: 0; } .container.ng-enter.ng-enter-active, .container.ng-leave, .container.ng-move.ng-move-active { opacity: 1; max-height: 30px; } (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search" /> <div ng-repeat="name in names | filter:search" class="container"> {{ name }} </div> </div> </div> (app.js) angular.module('myApp', ['ngAnimate']) .controller('Ctrl', function($scope) { $scope.names = [ 'Jake', 'Henry', 'Roger', 'Joe', 'Robert', 'John' ]; });
Since the animation is accomplished through the use of CSS transitions, you can tap into the CSS class staggering that is afforded to you by adding the following to the style sheet:
(style.css) .container.ng-enter-stagger, .container.ng-leave-stagger, .container.ng-move-stagger { transition-delay: 0.2s; transition-duration: 0; }
JSFiddle: http://jsfiddle.net/msfrisbie/emxsze4q/
For the example dataset, filtering with J
will cause multiple elements to be removed, as well as multiple elements to change their index. All of these changes correspond to an animation event. Since these animations occur simultaneously, AngularJS can take advantage of the fact that animations are queued up and executed in batches within a single reflow to compensate for the fact that reflows are computationally expensive.
The -stagger
classes essentially act as shims for successive animations. Instead of running all the animations in parallel, they are run serially, delimited by the additional stagger transition.
It is also possible to stagger animations using keyframes. This can be accomplished as follows:
(style.css) .container.ng-enter-stagger, .container.ng-leave-stagger, .container.ng-move-stagger { animation-delay: 0.2s; animation-duration: 0; } .container.ng-leave { animation: 0.5s repeat-leave; } .container.ng-enter { animation: 0.5s repeat-enter; } .container.ng-move { animation: 0.5s repeat-move; } @keyframes repeat-enter { from { opacity: 0; max-height: 0; } to { opacity: 1; max-height: 30px; } } @keyframes repeat-leave { from { opacity: 1; max-height: 30px; } to { opacity: 0; max-height: 0; } } @keyframes repeat-move { from { opacity: 0; max-height: 0; } to { opacity: 1; max-height: 30px; } }
JSFiddle: http://jsfiddle.net/msfrisbie/bbetcp1m/
44.200.94.150