diff options
| author | Brian Ford | 2013-10-08 10:43:01 -0700 |
|---|---|---|
| committer | Brian Ford | 2013-10-08 10:49:33 -0700 |
| commit | ad5256452bb8f1d481d78e7ae15a59d288f0d8e9 (patch) | |
| tree | f833faa2911c9e4b8fe8e838cffb5b735c586c9e | |
| parent | 80d2c85e3c04964631e822b57ff1fd1996240074 (diff) | |
| download | angular.js-ad5256452bb8f1d481d78e7ae15a59d288f0d8e9.tar.bz2 | |
feat(tutorial): add step 12 of the phonecat tutorial
| -rw-r--r-- | docs/content/tutorial/step_12.ngdoc | 419 | ||||
| -rw-r--r-- | docs/src/templates/js/docs.js | 2 |
2 files changed, 420 insertions, 1 deletions
diff --git a/docs/content/tutorial/step_12.ngdoc b/docs/content/tutorial/step_12.ngdoc new file mode 100644 index 00000000..af6c3faa --- /dev/null +++ b/docs/content/tutorial/step_12.ngdoc @@ -0,0 +1,419 @@ +@ngdoc overview +@name Tutorial: 12 - Applying Animations +@description + +<ul doc-tutorial-nav="12"></ul> + + +In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript +animations on top of the template code we created before. + + +<div doc-tutorial-reset="12"></div> + + +Now that everything is set in place for a fully functional web application, we can attach CSS and JavaScript +animations to common directives that are used to render our application. AngularJS comes bundled with an +additional JavaScript file called `angular-animate.js` which, when included into the website and set as +a dependency with the application module, will enable animations throughout the application. + +Common `ng` directives automatically trigger hooks for animations to tap into. When an animation is found +then the animation will run in between the standard DOM operation that is being issued on the element at +the given time (e.g. inserting and removing nodes on ngRepeat or adding and removing classes on ngClass). + +The most important changes are listed below. You can see the full diff on +{@link https://github.com/angular/angular-phonecat/compare/step-11...step-12 GitHub}: + + +## How Animations work with `ngAnimate` + +To get an idea of how animations work with AngularJS, please read the +{@link guide/animations AngularJS Animation Guide} first. + + +## Template + +The changes required within the HTML template code is to link the asset files which define the animations as well +as the `angular-animate.js` file. The animation module, known as `ngAnimate`, is defined within +`angular-animate.js` and contains the code necessary to make your application become animation aware. + +Here's what needs to changed in the index file: + +__`app/index.html`.__ +<pre> +... + <!-- jQuery is used for JavaScript animations (include this before angular.js) --> + <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> + + <!-- required module to enable animation support in AngularJS --> + <script src="lib/angular/angular-animate.js"></script> + + <!-- for JavaScript Animations --> + <script src="js/animations.js"></script> + + <!-- for CSS Transitions and/or Keyframe Animations --> + <link rel="stylesheet" href="css/animations.css"> +... +</pre> + +Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`). +But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before +with `ngResource`. + +## Module & Animations + +__`app/js/animations.js`.__ +<pre> +angular.module('phonecatAnimations', ['ngAnimate']). + // ... + // this module will later be used to define animations + // ... +</pre> + +And now let's attach this module to our application module... + +__`app/js/app.js`.__ +<pre> +// ... +angular.module('phonecat', [ + 'ngRoute', + + 'phonecatAnimations', + 'phonecatControllers', + 'phonecatFilters', + 'phonecatServices', +]). +// ... +</pre> + +Now, the phonecat module is animation aware. Let's make some animations! + + +## Animating ngRepeat with CSS Transition Animations + +We'll start off by adding CSS transition animations to our `ngRepeat` directive present on the `phone-list.html` page. +First let's add an extra CSS class to our repeated element so that we can hook into it with our CSS animation code. + +__`app/partials/phone-list.html`.__ +<pre> +<!-- + Let's change the repeater HTML to include a new CSS class + which we will later use for animations: +--> +<ul class="phones"> + <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" + class="thumbnail phone-listing"> + <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a> + <a href="#/phones/{{phone.id}}">{{phone.name}}</a> + <p>{{phone.snippet}}</p> + </li> +</ul> + +</pre> + +Notice how we added the `phone-listing` CSS class? This is all we need in our HTML code to get animations working. + +Now for the actual CSS transition animation code: + +__`app/css/animations.css`__ +<pre> +.phone-listing.ng-enter, +.phone-listing.ng-leave, +.phone-listing.ng-move { + -webkit-transition: 0.5s linear all; + -moz-transition: 0.5s linear all; + -o-transition: 0.5s linear all; + transition: 0.5s linear all; +} + +.phone-listing.ng-enter, +.phone-listing.ng-move { + opacity: 0; + height: 0; + overflow: hidden; +} + +.phone-listing.ng-move.ng-move-active, +.phone-listing.ng-enter.ng-enter-active { + opacity: 1; + height: 120px; +} + +.phone-listing.ng-leave { + opacity: 1; + overflow: hidden; +} + +.phone-listing.ng-leave.ng-leave-active { + opacity: 0; + height: 0; + padding-top: 0; + padding-bottom: 0; +} +</pre> + +As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are +inserted info and removed from the list: + + * The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page. + * The `ng-move` class is applied when items are moved around in the list. + * The `ng-leave` class is applied when they're removed from the list. + +The phone listing items are added and removed depending on the data passed to the `ng-repeat` attribute. +For example, if the filter data changes the items will be animated in and out of the repeat list. + +Something important to note is that when an animation occurs, two sets of CSS classes +are added to the element: + + 1. a "starting" class that represents the style at the beginning of the animation + 2. an "active" class that represents the style at the end of the animation + +The name of the starting class is the name of event that is fired (like `enter`, `move` or `leave`) prefixed with +`ng-`. So an `enter` event will result in a class called `ng-enter`. + +The active class name is the same as the starting class's but with an `-active` suffix. +This two-class CSS naming convention allows the developer to craft an animation, beginning to end. + +In our example above, elements expand from a height of **0** to **120 pixels** when items are added or moved, +around and collapsing the items before removing them from the list. +There's also a nice fade-in and fade-out effect that also occurs at the same time. All of this is handled +by the CSS transition declarations at the top of the example code above. + +Although most modern browsers have good support for {@link http://caniuse.com/#feat=css-transitions CSS transitions} +and {@link http://caniuse.com/#feat=css-animation CSS animations}, IE9 and earlier do not. +If you want animations that are backwards-compatible with older browsers, consider using JavaScript-based animations, +which are described in detail below. + + +## Animating `ngView` with CSS Keyframe Animations + +Next let's add an animation for transitions between route changes in `ngView`. + +To start, let's add a new CSS class to our HTML like we did in the example above. +This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive. +In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our +animations between view changes. + +__`app/partials/phone-list.html`.__ +<pre> +<div class="view-container"> + <div ng-view class="view-frame"></div> +</div> +</pre> + +With this change, the `ng-view` directive is nested inside a parent element with +a `view-container` CSS class. This class adds a `position: relative` style so that the positioning of the `ng-view` +is relative to this parent as it animates transitions. + +With this in place, let's add the CSS for this transition animation to our `animations.css` file: + +__`app/css/animations.css`.__ +<pre> +.view-container { + position: relative; +} + +.view-frame.ng-enter, .view-frame.ng-leave { + background: white; + position: absolute; + top: 0; + left: 0; + right: 0; +} + +.view-frame.ng-enter { + -webkit-animation: 0.5s fade-in; + -moz-animation: 0.5s fade-in; + -o-animation: 0.5s fade-in; + animation: 0.5s fade-in; + z-index: 100; +} + +.view-frame.ng-leave { + -webkit-animation: 0.5s fade-out; + -moz-animation: 0.5s fade-out; + -o-animation: 0.5s fade-out; + animation: 0.5s fade-out; + z-index:99; +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} +@-moz-keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} +@-webkit-keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} +@-moz-keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} +@-webkit-keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +/* don't forget about the vendor-prefixes! */ +</pre> + +Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the ordinary thing +here is that we're using absolute positioning to position the next page (identified via `ng-enter`) on top of the +previous page (the one that has the `ng-leave` class) while performing a cross fade animation in between. So +as the previous page is just about to be removed, it fades out while the new page fades in right on top of it. +Once the leave animation is over then element is removed and once the enter animation is complete then the +`ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to be position itself +with its default CSS code (so no more absolute positioning once the animation is over). This works fluidly so +that pages flow naturally between route changes without anything jumping around. + +The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time a new page is +loaded the ng-view directive will create a copy of itself, download the template and append the contents. This +ensures that all views are contained within a single HTML element which allows for easy animation control. + +For more on CSS animations, see the +{@link http://docs.webplatform.org/wiki/css/properties/animations Web Platform documentation}. + + +## Animating `ngClass` with JavaScript + +Let's add another animation to our application. Switching to our `phone-detail.html` page, +we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page, +the profile phone image changes. But how can we change this around to add animations? + +Lets think about it first, +basically when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly +selected thumbnail image. +The best way to specify state changes within HTML is to use classes. +Much like before, how we used a CSS class to specify +an animation, this time the animation will occur whenever the CSS class itself changes. + +Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added to the matching +profile image and the animation plays. + +Let's get started and tweak our HTML code on the `phone-detail.html` page first: + +__`app/partials/phone-detail.html`.__ +<pre> +<!-- We're only changing the top of the file --> +<div class="phone-images"> + <img ng-src="{{img}}" + class="phone" + ng-repeat="img in phone.images" + ng-class="{active:mainImageUrl==img}"> +</div> + +<h1>{{phone.name}}</h1> + +<p>{{phone.description}}</p> + +<ul class="phone-thumbs"> + <li ng-repeat="img in phone.images"> + <img ng-src="{{img}}" ng-mouseenter="setImage(img)"> + </li> +</ul> +</pre> + +Just like with the thumbnails, we're using a repeater to display **all** the profile images as a list, however we're +not animating any repeat-related animations. Instead, we're keeping our eye on the ng-class directive since whenever +the `active` class is true then it will be applied to the element and will render as visible. Otherwise, the profile image +is hidden. In our case, there is always one element that has the active class, and, therefore, there will always +be one phone profile image visible on screen at all times. + +When the active class is added to the element, the `active-add` and the `active-add-active` classes are added just before +to signal AngularJS to fire off an animation. When removed, the `active-remove` and the `active-remove-active` classes +are applied to the element which in turn trigger another animation. + +You may be thinking that we're just going to create another CSS-enabled animation. +Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled animations with the `animation()` module method. + +__`app/js/animations.js`.__ +<pre> +angular.module('phonecatAnimations', ['ngAnimate']) + + .animation('.phone', function() { + return { + addClass : function(element, className, done) { + if(className != 'active') { + return; + } + element.css({ + position: 'absolute', + top: 500, + left: 0, + display: 'block' + }); + jQuery(element).animate({ + top: 0 + }, done); + + return function(cancel) { + if(cancel) element.stop(); + }; + }, + removeClass : function(element, className, done) { + if(className != 'active') return; + element.css({ + position: 'absolute', + left: 0, + top: 0 + }); + jQuery(element).animate({ + top: -500 + }, done); + + return function(cancel) { + if(cancel) element.stop(); + }; + } + }; + }); +</pre> + +Note that we're using {@link http://jquery.com/ jQuery} to implement the animation. jQuery +isn't required to do JavaScript animations with AngularJS, but we're going to use it because writing +your own JavaScript animation library is beyond the scope of this tutorial. For more on +`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}. + +<div class="alert alert-error"> + <h4>Important:</h4> + Be sure to use jQuery version `1.10.2`. AngularJS does not yet support jQuery `2.x`. +</div> + +The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed +on the element that contains the class we registered, which is in this case `.phone`. When the `.active` +class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will +be fired with `element` passed in as a parameter to that callback. The last parameter passed in is the +`done` callback function. The purpose of `done` is so you can let Angular know when the JavaScript +animation has ended by calling it. + +The `removeClass` callback works the same way, but instead gets triggered when a class is removed +from the element. + +Within your JavaScript callback, you create the animation by manipulating the DOM. In the code above, +that's what the `element.css()` and the `element.animate()` are doing. The callback positions the next +element with an offset of `500 pixels` and animates both the previous and the new items together by +shifting each item up `500 pixels`. This results in a conveyor-belt like animation. After the `animate` +function does its business, it calls `done`. + +Notice that `addClass` and `removeClass` each return a function. This is an **optional** function that's +called when the animation is cancelled (when another animation takes place on the same element) +as well as when the animation has completed. A boolean parameter is passed into the function which +lets the developer know if the animation was cancelled or not. This function can be used to +do any cleanup necessary for when the animation finishes. + + +# Summary + +There you have it! Animations are in place. Hopefully this has shown you how you can improve +your AngularJS web application to have an awesome layer of animated interactivity. + +<ul doc-tutorial-nav="12"></ul> diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js index 24013480..fb095090 100644 --- a/docs/src/templates/js/docs.js +++ b/docs/src/templates/js/docs.js @@ -203,7 +203,7 @@ docsApp.directive.docTutorialNav = function(templateMerge) { '', 'step_00', 'step_01', 'step_02', 'step_03', 'step_04', 'step_05', 'step_06', 'step_07', 'step_08', 'step_09', - 'step_10', 'step_11', 'the_end' + 'step_10', 'step_11', 'step_12', 'the_end' ]; return { compile: function(element, attrs) { |
