Animating Angular

@NickSeegmiller

About Me

Born and raised in Utah

CS Degree from University of Utah

Married 10 years with 8 year old daughter

Perl web development for Backcountry.com

C/C++ game development for Sensory Sweep

C/Java public safety applications for Spillman Technologies

Angular + Android home automation app development for Vivint

Overview

  • History
  • Getting started
  • CSS based animations
  • JS based animations
  • Mixing CSS and JS animations
  • "Pick best" between CSS and JS animations

ngAnimate history

Initial "unstable" support added in 1.1.4 quantum-manipulation (Apr 2013)

 

Required ng-animate directive that you passed animation classes to

 

"Proper" support in 1.2.0 timely-delivery (Nov 2013)

 

Current version we know and love

 

"Big" changes coming in 1.4

Getting started

angular.module('angular-examples', ['ngRoute', 'ngAnimate']);
<script src="<cdn-path>/angularjs/1.3.14/angular.js"></script>
<script src="<cdn-path>/angularjs/1.3.14/angular-route.min.js"></script>
<script src="<cdn-path>/angularjs/1.3.14/angular-animate.min.js"></script>

app.js

index.html

Structural transitions

  • ngIf
  • ngView
  • ngInclude
  • ngSwitch
  • ngRepeat (& move)
  • ngMessage

enter & leave

HTML

<div ng-if="ngif" class="example-block fade-in-out">
    <img src="img/plainDoge.jpg">
    <h1>ng-if</h1>
</div>

ng-if

<div ng-include="nginclude" class="fade-in-out"></div>

ng-include

<div ng-view class="full-height no-overflow fade-in-out"></div>

ng-view

etc

Transitions (pure CSS)

// Fade in and out done with pure CSS transitions
.fade-in-out.ng-enter, .fade-in-out.ng-leave {
  -webkit-transition: 0.5s linear all;
  transition: 0.5s linear all;
}
.fade-in-out.ng-enter {
  opacity: 0;
}
.fade-in-out.ng-leave {
  opacity: 1;
}
.fade-in-out.ng-enter.ng-enter-active {
  opacity: 1;
}
.fade-in-out.ng-leave.ng-leave-active {
  opacity: 0;
}

Transitions (Compass/SCSS)

$transition-time: .5s;
.opacity-fade {
  &.ng-enter {
    opacity: 0;
    @include transition(opacity $transition-time);
    &.ng-enter-active {
      opacity: 1;
    }
  }
  &.ng-leave {
    opacity: 1;
    @include transition(opacity $transition-time);
    &.ng-leave-active {
      opacity: 0;
    }
  }
}

Animations

@keyframes fade-in {
  0% { opacity: 0; }
  90% { opacity: .5; }
  100% { opacity: 1; }
}
@-webkit-keyframes fade-in {
  0% { opacity: 0; }
  90% { opacity: .5; }
  100% { opacity: 1; }
}
@keyframes fade-out {
  0% { opacity: 1; }
  90% { opacity: .5; }
  100% { opacity: 0; }
}
@-webkit-keyframes fade-out {
  0% { opacity: 1; }
  90% { opacity: .5; }
  100% { opacity: 0; }
}
$transition-time: .5s;
.keyframe-fade-in-out {
  &.ng-enter {
    -webkit-animation: $transition-time fade-in;
    animation: $transition-time fade-in;
  }
  &.ng-leave {
    -webkit-animation: $transition-time fade-out;
    animation: $transition-time fade-out;
  }
}

Gotcha the first - ngView

Both views are in the DOM at the same time

 

Consider "position: absolute;"

ngView

.opacity-fade-absolute {
  &.ng-enter {
    opacity: 0;
    position: absolute; // Here
    @include transition(opacity $transition-time);
    &.ng-enter-active {
      opacity: 1;
    }
  }
  &.ng-leave {
    opacity: 1;
    position: absolute; // Here
    @include transition(opacity $transition-time);
    &.ng-leave-active {
      opacity: 0;
    }
  }
}

ngView - Unique Animations

$scope.go = function (page, transitionClass) {
    if (transitionClass !== undefined) {
        $rootScope.transitionClass = transitionClass;
    }
    $location.path(page);
};
<div class="full-height no-overflow {{transitionClass}}" ng-view></div>

index.html

Controller

<li ng-click="go('/other-page', 'opacity-fade-absolute')">Other Page</li>

View

Class-based transitions

  • ngClass
  • ngShow & ngHide
  • form & ngModel
  • ngMessages

add & remove

Gotcha the second - ngShow

.opacity-fade {
  &.ng-show {
    opacity: 0;
    @include transition(opacity $transition-time);
    &.ng-show-active {
      opacity: 1;
    }
  }
  &.ng-hide {
    opacity: 1;
    @include transition(opacity $transition-time);
    &.ng-hide-active {
      opacity: 0;
    }
  }
}

This doesn't work!

ngShow/ngHide

Toggling ng-hide class

.fade-show {
  @include transition(opacity $transition-time);
  // Removing hide is showing the element
  &.ng-hide-remove {
    opacity: 0;
    &.ng-hide-remove-active {
      opacity: 1;
    }
  }
  // Adding hide is hiding the element
  &.ng-hide-add {
    opacity: 1;
    &.ng-hide-add-active {
      opacity: 0;
    }
  }
}

Gotcha the third - .ng-hide

.ng-hide {
  display: none !important;
}
.fade-show {
  @include transition(opacity $transition-time);
  // Removing hide is showing the element
  &.ng-hide-remove {
    opacity: 0;
    display: block !important; // Here
    &.ng-hide-remove-active {
      opacity: 1;
    }
  }
  // Adding hide is hiding the element
  &.ng-hide-add {
    opacity: 1;
    display: block !important; // Here
    &.ng-hide-add-active {
      opacity: 0;
    }
  }
}

ngAnimate with JavaScript

Specifically the GreenSock Animation Platform (GSAP)

  • Super fast
  • Feature rich & robust
  • Works everywhere
  • Sequencing
  • Pausing animations
  • Lightweight
  • Good license

 

http://greensock.com/gsap

Setup

<div ng-if="fade-in" class="example-block fade-in-out-js">
    <img src="img/plainDoge.jpg">
    <h1>fade in/out js</h1>
</div>

View

angular.module(
    'angular-examples',
    ['angular-examples.animations', 'ngRoute', 'ngAnimate']
)
.constant('TweenMax', TweenMax)

app.js

JavaScript

angular.module('angular-examples.animations')
    .animation('.fade-in-out-js', ['TweenMax',
        function(TweenMax) {
            return {
                enter: function(element, done) {
                    // Some sort of animation
                },
                leave: function(element, done) {
                    // Some sort of animation
                }
            }
        }
    ])
;

Enter & Leave

enter: function(element, done) {
    TweenMax.fromTo(
        element,
        fadeTime,
        {opacity: 0},
        {opacity: 1, onComplete: done}
    );
},                
leave: function(element, done) {
    TweenMax.to(
        element,
        fadeTime,
        {opacity: 0, onComplete: done}
    );
}

Don't forget to call the done function!

Gotcha the fourth - show/hide

show: function(element, done) {
    // This is wrong
},                
hide: function(element, done) {
    // This is wrong
},
add: function(element, done) {
    // And this
},
remove: function(element, done) {
    // This too
},
ngHideAdd: function(element, done) {
    // Nope
},
ngHideRemove: function(element, done) {
    // Still nope
},

addClass/removeClass

//animation that can be triggered before the class is added
beforeAddClass: function(element, className, done) { },

//animation that can be triggered after the class is added
addClass: function(element, className, done) { },

//animation that can be triggered before the class is removed
beforeRemoveClass: function(element, className, done) { },

//animation that can be triggered after the class is removed
removeClass: function(element, className, done) { }

Show/Hide

removeClass: function(element, className, done) {
    if (className === 'ng-hide') {
        element.removeClass('ng-hide');
        TweenMax.fromTo(
            element,
            fadeTime,
            {opacity: 0},
            {opacity: 1, onComplete: done}
        );
    }
    else {
        done();
    }
},
beforeAddClass: function(element, className, done) {
    if (className === 'ng-hide') {
        TweenMax.to(
            element,
            fadeTime,
            {opacity: 0, onComplete: done}
        );
    }
    else {
        done();
    }
}

Sequencing

enter: function(element, done) {
    TweenMax.fromTo(
        element,
        time,
        {opacity: 0},
        {opacity: 1, onComplete: function() {
            TweenMax.fromTo(
                element,
                time,
                {x: 0, rotationZ: 0},
                {x: 200, rotationZ: 90, onComplete: function() {
                    TweenMax.fromTo(
                        element,
                        time,
                        {y: 0},
                        {y: 200, onComplete: done}
                    );
                }}
            )
    }});
},

Lots of properties at once!

enter: function(element, done) {
    TweenMax.fromTo(
        element,
        time,
        {
            x: 0, y: 0,
            scaleX: 1, scaleY: 1,
            skewX: 0, skewY: 0,
            rotationZ: 0, rotationY: 0
        },
        {
            x: 200, y: 300,
            scaleX: 2, scaleY: 3,
            skewX: "5deg", skewY: "10deg",
            rotationY: 45, rotationZ: 30,
            onComplete: done
        }
    );
},

Mixing the business!

<!-- mix css & js -->
<div ng-if="mixed" class="example-block mixed">
    <img src="img/plainDoge.jpg">
    <h1>mixed</h1>
</div>
.mixed {
  &.ng-enter {
    opacity: 0;
    @include transition(opacity $transition-time);
    &.ng-enter-active {
      opacity: 1;
    }
  }
  &.ng-leave {
    opacity: 1;
    @include transition(opacity $transition-time);
    &.ng-leave-active {
      opacity: 0;
    }
  }
}

Mixing - JavaScript

.animation('.mixed', ['TweenMax',
    function(TweenMax) {
        var transitionTime = 0.5;
        return {
            enter: function(element, done) {
                TweenMax.fromTo(
                    element,
                    transitionTime,
                    {scale: 1, x: 0, y: 0},
                    {scale: 2, x: element.width(), y: element.height(),
                        onComplete: done}
                );
            },
            leave: function(element, done) {
                TweenMax.to(
                    element,
                    transitionTime,
                    {scale: 1, x: 0, y: 0, onComplete: done}
                );
            }
        }
    }
])

Pick the best

$scope.hasTransitions = Modernizr.csstransitions;

Build custom Modernizr

http://modernizr.com/download/

Controller

<div ng-if="pickBest"
     class="example-block"
     ng-class="{
        'fade-in-out': hasTransitions,
        'fade-in-out-js': !hasTransitions}"
>

View

Short circuit JS Animation

angular.module('angular-examples.animations')
    .animation('.fade-in-out', ['TweenMax', 'Modernizr',
        function(TweenMax, Modernizr) {
            if (!Modernizr.csstransitions) {
                return {
                    enter: function(element, done) {
                        // Some sort of animation
                    },
                    leave: function(element, done) {
                        // Some sort of animation
                    }
                }
            }
            return {};
        }
    ])
;

Then just let CSS animation play

Resources

  • https://github.com/nseegmiller/angular-examples/tree/animating-angular
     
  • https://docs.angularjs.org/api/ngAnimate
     
  • http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html
     
  • http://greensock.com/get-started-js

Thanks!

Animating Angular

By Nick Seegmiller

Animating Angular

When asked about the best thing of Angular, many people are quick to point to its fantastic two-way binding feature. With ngAnimate, it is exceptionally easy to add a little spice to that great feature. I will show everyone how simple it is to get up and running with ngAnimate, demonstrate some of my favorite tips and tricks, and delve into some advanced strategies for combining CSS and JS animations using the GreenSock Animation Platform (GSAP).

  • 1,905