Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="angular-animate.js"></script> <script src="script.js"></script> <link rel="stylesheet" href="style.css"> </head> <body ng-app="mainModule" ng-controller="mainController"> <h3>1. Enter and Leave</h3> <label>Show the square: <input type="checkbox" ng-model="enterLeaveSwitch" /></label><br /> <br /> <div class="box animated-element" ng-if="enterLeaveSwitch"></div> <br /> <h3>2. Move</h3> <ul> <li class="animated-element" ng-repeat="item in customArray"> {{item}} </li> </ul> <button type="button" ng-click="onReverseArrayClick()">Reverse array</button><br /> <br /> <h3>3. Add Class and Remove Class</h3> <label>Show the square: <input type="checkbox" ng-model="addRemoveClassSwitch" /></label><br /> <br /> <div class="invisible-box animated-element" ng-class="addRemoveClassSwitch ? 'custom-class' : ''"></div> <br /> <h3>4. CSS keyframe animation</h3> <label>Show the square: <input type="checkbox" ng-model="enterLeaveCSSSwitch" /></label><br /> <br /> <div class="box animated-element-with-keyframes" ng-if="enterLeaveCSSSwitch"></div> </div> </body> </html>
angular.module("mainModule", ["ngAnimate"]) .controller("mainController", function ($scope) { $scope.customArray = ["First", "Second", "Third", "Fourth"]; $scope.onReverseArrayClick = function () { $scope.customArray.reverse(); }; });
* { box-sizing: border-box; } .box { width: 100px; height: 100px; background-color: lightskyblue; display: block; } .invisible-box { width: 100px; height: 100px; background-color: lightcoral; display: block; opacity: 0; } .custom-class { opacity: 1; } .animated-element.ng-enter, .animated-element.ng-leave, .animated-element.ng-move, .animated-element.custom-class-add, .animated-element.custom-class-remove { -webkit-transition: 1.5s linear all; transition: 1.5s linear all; } .animated-element.ng-enter { opacity: 0; } .animated-element.ng-enter.ng-enter-active { opacity: 1; } .animated-element.ng-leave { opacity: 1; } .animated-element.ng-leave.ng-leave-active { opacity: 0; } .animated-element.ng-move { opacity: 0; } .animated-element.ng-move.ng-move-active { opacity: 1; } .animated-element.ng-move-stagger { /* Set a 400ms delay between each successive move animation */ -webkit-transition-delay: 0.4s; transition-delay: 0.4s; /* * In case the stagger doesn't work then these two values * must be set to 0 to avoid an accidental CSS inheritance. */ -webkit-transition-duration: 0s; transition-duration: 0s; } .animated-element.custom-class-add { opacity: 0; } .animated-element.custom-class-add.custom-class-add-active { opacity: 1; } .animated-element.custom-class-remove { opacity: 1; } .animated-element.custom-class-remove.custom-class-remove-active { opacity: 0; } @-webkit-keyframes blinkAndGrow { from { background-color: lightskyblue; } 25%, 75% { transform: scale(1.1); background-color: blue; } 50% { transform: scale(1.0); background-color: lightskyblue; } to { background-color: lightskyblue; } } @keyframes blinkAndGrow { from { background-color: lightskyblue; } 25%, 75% { transform: scale(1.1); background-color: blue; } 50% { transform: scale(1.0); background-color: lightskyblue; } to { background-color: lightskyblue; } } @-webkit-keyframes shrinkAndDisappear { from { opacity: 1; } 70% { transform: scale(0.8); opacity: 0.8; } to { transform: scale(0.0); opacity: 0; } } @keyframes shrinkAndDisappear { from { opacity: 1; } 70% { transform: scale(0.8); opacity: 0.8; } to { transform: scale(0.0); opacity: 0; } } .animated-element-with-keyframes.ng-enter { -webkit-animation: blinkAndGrow 1.0s linear; animation: blinkAndGrow 1.0s linear; } .animated-element-with-keyframes.ng-leave { -webkit-animation: shrinkAndDisappear 0.8s linear; animation: shrinkAndDisappear 0.8s linear; }

Example


Description


Some AngularJS directives directly support CSS3 transitions and CSS3 keyframe animations. In the official documentation of the ngAnimate module you can see which animations are supported by each directive.

To make the animations work, first of all we need to include the angular-animate.js script file and add the ngAnimate module as a dependency of our application module. Then we need to define the CSS classes for the transitions and keyframe animations.

Let's start by taking a look at point 1 of the example. Here we want to see the enter and leave animations in action. We use the ng-if directive to add or remove a box, represented by a div element, depending on the value of a checkbox. That div has two CSS classes applied to it: box and animated-element. The first class simply makes the div look like a box, while the second one (animated-element) is useful to define the CSS transition to animate the enter and leave events. The class could have been named my-custom-animation-class instead of animated-element, its purpose is just to identify an element that is animated and its name is not important. Here is the structure of the CSS selectors used to define the transition associated to the animated-element class for the enter event:

.animated-element.ng-enter
{
  -webkit-transition: 1.5s linear all;
  transition: 1.5s linear all;

  opacity: 0;
}

.animated-element.ng-enter.ng-enter-active
{
  opacity: 1;
}

We call ng-enter the setup class and ng-enter-active the active class. These CSS selectors allow us to specify that we want to apply this animation only to the elements that have also the animated-element class. The setup class defines the transition itself as well as the initial values of the animated properties (opacity in this case) while the active class defines the final values of the animated properties at the end of the animation. The transition will be between these values. This is what basically happens when our div is added to the DOM:

  1. the ng-enter class is added to the div;
  2. AngularJS performs all the necessary steps to initialize the animation (through the $animate service);
  3. the ng-enter-active class is added to the div and this triggers the animation;
  4. after the animation ends, both ng-enter and ng-enter-active are removed from the div.

The same happens for the leave event, but in that case ng-leave will be the setup class and ng-leave-active will be the active class.

Now we can write the basic structure of the CSS selectors to define a CSS transition for AngularJS:

/* Setup class */
.my-custom-class-name.ng-{event name}
{
  /* CSS transition definition */
  -webkit-transition: ...;
  transition: ...;

  /* Animated properties initial values */
  my-property-1: ...;
  my-property-2: ...;
}

/* Active class */
.my-custom-class-name.ng-{event name}.ng-{event name}-active
{
  /* Animated properties final values */
  my-property-1: ...;
  my-property-2: ...;
}

In point 2 of the example, we can see the animation for the move event. This is supported by the ng-repeat directive. What we do here is simply create an array (customArray) of items and call the reverse method to move the items of the array in different positions and see the animation. The structure of the CSS to define the transition is the same as the one used for the enter and leave events with just one addition, the ng-move-stagger class. Here we've defined a staggering animation. If we didn't define the ng-move-stagger class, each item in the array would fade-in at the same time in its new position after the call to the reverse method, while with a staggering animation we are able to add a delay to each successive item animation so the result is that we see that each item starts to fade-in after a short time compared to the previous one. Here is the structure of the move animation in the CSS:

.animated-element.ng-move
{
  -webkit-transition: 1.5s linear all;
  transition: 1.5s linear all;

  opacity: 0;
}

.animated-element.ng-move-stagger
{
  /* Set a 400ms delay between each successive move animation */
  -webkit-transition-delay: 0.4s;
  transition-delay: 0.4s;

  /*
   * In case the stagger doesn't work then these two values
   * must be set to 0 to avoid an accidental CSS inheritance.
  */
  -webkit-transition-duration: 0s;
  transition-duration: 0s;
}

.animated-element.ng-move.ng-move-active
{
  opacity: 1;
}

The transition-delay and -webkit-transition-delay CSS properties define the delay between each successive move animation. The transition-duration and -webkit-transition-duration properties are there just to reset those values in case a CSS inheritance sets them to different values that make the staggering animation stop working. This is just a good habit to avoid surprises.

In point 3 of the example we can see how we can manage class-based animations. We can animate an element even when a CSS class is added to it or removed from it so we have the add and remove animation events. The structure of the CSS is very similar to what we've already seen:

/* Setup class */
.my-custom-class-name.{class name}-{add|remove}
{
  /* CSS transition definition */
  -webkit-transition: ...;
  transition: ...;

  /* Animated properties initial values */
  my-property-1: ...;
  my-property-2: ...;
}

/* Active class */
.my-custom-class-name.{class name}-{add|remove}.{class name}-{add|remove}-active
{
  /* Animated properties final values */
  my-property-1: ...;
  my-property-2: ...;
}

To be more specific, here's what we have in the example for the add event:

.animated-element.custom-class-add
{
  -webkit-transition: 1.5s linear all;
  transition: 1.5s linear all;

  opacity: 0;
}

.animated-element.custom-class-add.custom-class-add-active
{
  opacity: 1;
}

And this is what we have for the remove event:

.animated-element.custom-class-remove
{
  -webkit-transition: 1.5s linear all;
  transition: 1.5s linear all;

  opacity: 0;
}

.animated-element.custom-class-remove.custom-class-remove-active
{
  opacity: 1;
}

This is what we're saying here: when a class named custom-class is added to an element that also has the animated-element class, then apply the add CSS transition, while if custom-class is removed then apply the remove transition. Here are the steps performed by AngularJS when our custom-class is added to the div:

  1. the custom-class-add class is added to the div;
  2. AngularJS waits for a single animation frame (through the $animate service);
  3. the custom-class and custom-class-add-active classes are added to the div and this triggers the animation;
  4. after the animation ends, both custom-class-add and custom-class-add-active are removed from the div while custom-class remains.

Since at the end of the animation custom-class is kept on the element (we wanted to add that CSS class to the div and that's why the animation process started) the style defined in that class is what will be applied to the element after the animation.

In point 4 of the example we see that we can also use the CSS keyframe animations to animate our elements. In this case we don't need the active class, but just the setup class. Here is the basic structure of a CSS keyframe animation defined for AngularJS:

@-webkit-keyframes ...
{
  ...
}

@keyframes ...
{
  ...
}

/* Setup class */
.my-custom-class-name.ng-{event name}
{
  /* CSS keyframe animation definition */
  -webkit-animation: ...;
  animation: ...;

  /* Animated properties initial values */
  my-property-1: ...;
  my-property-2: ...;
}

In the example we've defined a keyframe animation for both the enter and leave events. In case you want to define a staggering keyframe animation, you can do it as we did for the CSS transition, but in the -stagger class you need to use animation-delay, -webkit-animation-delay, animation-duration and -webkit-animation-duration instead of transition-delay, -webkit-transition-delay, transition-duration and -webkit-transition-duration.

We've just seen how we can take advantage of the animations already supported by the AngularJS directives and in the next example we'll see how to animate our own directives.