Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit ad52564

Browse files
committedOct 8, 2013
feat(tutorial): add step 12 of the phonecat tutorial
1 parent 80d2c85 commit ad52564

File tree

2 files changed

+420
-1
lines changed

2 files changed

+420
-1
lines changed
 

‎docs/content/tutorial/step_12.ngdoc

+419
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,419 @@
1+
@ngdoc overview
2+
@name Tutorial: 12 - Applying Animations
3+
@description
4+
5+
<ul doc-tutorial-nav="12"></ul>
6+
7+
8+
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
9+
animations on top of the template code we created before.
10+
11+
12+
<div doc-tutorial-reset="12"></div>
13+
14+
15+
Now that everything is set in place for a fully functional web application, we can attach CSS and JavaScript
16+
animations to common directives that are used to render our application. AngularJS comes bundled with an
17+
additional JavaScript file called `angular-animate.js` which, when included into the website and set as
18+
a dependency with the application module, will enable animations throughout the application.
19+
20+
Common `ng` directives automatically trigger hooks for animations to tap into. When an animation is found
21+
then the animation will run in between the standard DOM operation that is being issued on the element at
22+
the given time (e.g. inserting and removing nodes on ngRepeat or adding and removing classes on ngClass).
23+
24+
The most important changes are listed below. You can see the full diff on
25+
{@link https://github.com/angular/angular-phonecat/compare/step-11...step-12 GitHub}:
26+
27+
28+
## How Animations work with `ngAnimate`
29+
30+
To get an idea of how animations work with AngularJS, please read the
31+
{@link guide/animations AngularJS Animation Guide} first.
32+
33+
34+
## Template
35+
36+
The changes required within the HTML template code is to link the asset files which define the animations as well
37+
as the `angular-animate.js` file. The animation module, known as `ngAnimate`, is defined within
38+
`angular-animate.js` and contains the code necessary to make your application become animation aware.
39+
40+
Here's what needs to changed in the index file:
41+
42+
__`app/index.html`.__
43+
<pre>
44+
...
45+
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
46+
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
47+
48+
<!-- required module to enable animation support in AngularJS -->
49+
<script src="lib/angular/angular-animate.js"></script>
50+
51+
<!-- for JavaScript Animations -->
52+
<script src="js/animations.js"></script>
53+
54+
<!-- for CSS Transitions and/or Keyframe Animations -->
55+
<link rel="stylesheet" href="css/animations.css">
56+
...
57+
</pre>
58+
59+
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
60+
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
61+
with `ngResource`.
62+
63+
## Module & Animations
64+
65+
__`app/js/animations.js`.__
66+
<pre>
67+
angular.module('phonecatAnimations', ['ngAnimate']).
68+
// ...
69+
// this module will later be used to define animations
70+
// ...
71+
</pre>
72+
73+
And now let's attach this module to our application module...
74+
75+
__`app/js/app.js`.__
76+
<pre>
77+
// ...
78+
angular.module('phonecat', [
79+
'ngRoute',
80+
81+
'phonecatAnimations',
82+
'phonecatControllers',
83+
'phonecatFilters',
84+
'phonecatServices',
85+
]).
86+
// ...
87+
</pre>
88+
89+
Now, the phonecat module is animation aware. Let's make some animations!
90+
91+
92+
## Animating ngRepeat with CSS Transition Animations
93+
94+
We'll start off by adding CSS transition animations to our `ngRepeat` directive present on the `phone-list.html` page.
95+
First let's add an extra CSS class to our repeated element so that we can hook into it with our CSS animation code.
96+
97+
__`app/partials/phone-list.html`.__
98+
<pre>
99+
<!--
100+
Let's change the repeater HTML to include a new CSS class
101+
which we will later use for animations:
102+
-->
103+
<ul class="phones">
104+
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp"
105+
class="thumbnail phone-listing">
106+
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
107+
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
108+
<p>{{phone.snippet}}</p>
109+
</li>
110+
</ul>
111+
112+
</pre>
113+
114+
Notice how we added the `phone-listing` CSS class? This is all we need in our HTML code to get animations working.
115+
116+
Now for the actual CSS transition animation code:
117+
118+
__`app/css/animations.css`__
119+
<pre>
120+
.phone-listing.ng-enter,
121+
.phone-listing.ng-leave,
122+
.phone-listing.ng-move {
123+
-webkit-transition: 0.5s linear all;
124+
-moz-transition: 0.5s linear all;
125+
-o-transition: 0.5s linear all;
126+
transition: 0.5s linear all;
127+
}
128+
129+
.phone-listing.ng-enter,
130+
.phone-listing.ng-move {
131+
opacity: 0;
132+
height: 0;
133+
overflow: hidden;
134+
}
135+
136+
.phone-listing.ng-move.ng-move-active,
137+
.phone-listing.ng-enter.ng-enter-active {
138+
opacity: 1;
139+
height: 120px;
140+
}
141+
142+
.phone-listing.ng-leave {
143+
opacity: 1;
144+
overflow: hidden;
145+
}
146+
147+
.phone-listing.ng-leave.ng-leave-active {
148+
opacity: 0;
149+
height: 0;
150+
padding-top: 0;
151+
padding-bottom: 0;
152+
}
153+
</pre>
154+
155+
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
156+
inserted info and removed from the list:
157+
158+
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
159+
* The `ng-move` class is applied when items are moved around in the list.
160+
* The `ng-leave` class is applied when they're removed from the list.
161+
162+
The phone listing items are added and removed depending on the data passed to the `ng-repeat` attribute.
163+
For example, if the filter data changes the items will be animated in and out of the repeat list.
164+
165+
Something important to note is that when an animation occurs, two sets of CSS classes
166+
are added to the element:
167+
168+
1. a "starting" class that represents the style at the beginning of the animation
169+
2. an "active" class that represents the style at the end of the animation
170+
171+
The name of the starting class is the name of event that is fired (like `enter`, `move` or `leave`) prefixed with
172+
`ng-`. So an `enter` event will result in a class called `ng-enter`.
173+
174+
The active class name is the same as the starting class's but with an `-active` suffix.
175+
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
176+
177+
In our example above, elements expand from a height of **0** to **120 pixels** when items are added or moved,
178+
around and collapsing the items before removing them from the list.
179+
There's also a nice fade-in and fade-out effect that also occurs at the same time. All of this is handled
180+
by the CSS transition declarations at the top of the example code above.
181+
182+
Although most modern browsers have good support for {@link http://caniuse.com/#feat=css-transitions CSS transitions}
183+
and {@link http://caniuse.com/#feat=css-animation CSS animations}, IE9 and earlier do not.
184+
If you want animations that are backwards-compatible with older browsers, consider using JavaScript-based animations,
185+
which are described in detail below.
186+
187+
188+
## Animating `ngView` with CSS Keyframe Animations
189+
190+
Next let's add an animation for transitions between route changes in `ngView`.
191+
192+
To start, let's add a new CSS class to our HTML like we did in the example above.
193+
This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive.
194+
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
195+
animations between view changes.
196+
197+
__`app/partials/phone-list.html`.__
198+
<pre>
199+
<div class="view-container">
200+
<div ng-view class="view-frame"></div>
201+
</div>
202+
</pre>
203+
204+
With this change, the `ng-view` directive is nested inside a parent element with
205+
a `view-container` CSS class. This class adds a `position: relative` style so that the positioning of the `ng-view`
206+
is relative to this parent as it animates transitions.
207+
208+
With this in place, let's add the CSS for this transition animation to our `animations.css` file:
209+
210+
__`app/css/animations.css`.__
211+
<pre>
212+
.view-container {
213+
position: relative;
214+
}
215+
216+
.view-frame.ng-enter, .view-frame.ng-leave {
217+
background: white;
218+
position: absolute;
219+
top: 0;
220+
left: 0;
221+
right: 0;
222+
}
223+
224+
.view-frame.ng-enter {
225+
-webkit-animation: 0.5s fade-in;
226+
-moz-animation: 0.5s fade-in;
227+
-o-animation: 0.5s fade-in;
228+
animation: 0.5s fade-in;
229+
z-index: 100;
230+
}
231+
232+
.view-frame.ng-leave {
233+
-webkit-animation: 0.5s fade-out;
234+
-moz-animation: 0.5s fade-out;
235+
-o-animation: 0.5s fade-out;
236+
animation: 0.5s fade-out;
237+
z-index:99;
238+
}
239+
240+
&#64;keyframes fade-in {
241+
from { opacity: 0; }
242+
to { opacity: 1; }
243+
}
244+
&#64;-moz-keyframes fade-in {
245+
from { opacity: 0; }
246+
to { opacity: 1; }
247+
}
248+
&#64;-webkit-keyframes fade-in {
249+
from { opacity: 0; }
250+
to { opacity: 1; }
251+
}
252+
253+
&#64;keyframes fade-out {
254+
from { opacity: 1; }
255+
to { opacity: 0; }
256+
}
257+
&#64;-moz-keyframes fade-out {
258+
from { opacity: 1; }
259+
to { opacity: 0; }
260+
}
261+
&#64;-webkit-keyframes fade-out {
262+
from { opacity: 1; }
263+
to { opacity: 0; }
264+
}
265+
266+
&#47;&#42; don't forget about the vendor-prefixes! &#42;&#47;
267+
</pre>
268+
269+
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the ordinary thing
270+
here is that we're using absolute positioning to position the next page (identified via `ng-enter`) on top of the
271+
previous page (the one that has the `ng-leave` class) while performing a cross fade animation in between. So
272+
as the previous page is just about to be removed, it fades out while the new page fades in right on top of it.
273+
Once the leave animation is over then element is removed and once the enter animation is complete then the
274+
`ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to be position itself
275+
with its default CSS code (so no more absolute positioning once the animation is over). This works fluidly so
276+
that pages flow naturally between route changes without anything jumping around.
277+
278+
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time a new page is
279+
loaded the ng-view directive will create a copy of itself, download the template and append the contents. This
280+
ensures that all views are contained within a single HTML element which allows for easy animation control.
281+
282+
For more on CSS animations, see the
283+
{@link http://docs.webplatform.org/wiki/css/properties/animations Web Platform documentation}.
284+
285+
286+
## Animating `ngClass` with JavaScript
287+
288+
Let's add another animation to our application. Switching to our `phone-detail.html` page,
289+
we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page,
290+
the profile phone image changes. But how can we change this around to add animations?
291+
292+
Lets think about it first,
293+
basically when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly
294+
selected thumbnail image.
295+
The best way to specify state changes within HTML is to use classes.
296+
Much like before, how we used a CSS class to specify
297+
an animation, this time the animation will occur whenever the CSS class itself changes.
298+
299+
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added to the matching
300+
profile image and the animation plays.
301+
302+
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
303+
304+
__`app/partials/phone-detail.html`.__
305+
<pre>
306+
<!-- We're only changing the top of the file -->
307+
<div class="phone-images">
308+
<img ng-src="{{img}}"
309+
class="phone"
310+
ng-repeat="img in phone.images"
311+
ng-class="{active:mainImageUrl==img}">
312+
</div>
313+
314+
<h1>{{phone.name}}</h1>
315+
316+
<p>{{phone.description}}</p>
317+
318+
<ul class="phone-thumbs">
319+
<li ng-repeat="img in phone.images">
320+
<img ng-src="{{img}}" ng-mouseenter="setImage(img)">
321+
</li>
322+
</ul>
323+
</pre>
324+
325+
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a list, however we're
326+
not animating any repeat-related animations. Instead, we're keeping our eye on the ng-class directive since whenever
327+
the `active` class is true then it will be applied to the element and will render as visible. Otherwise, the profile image
328+
is hidden. In our case, there is always one element that has the active class, and, therefore, there will always
329+
be one phone profile image visible on screen at all times.
330+
331+
When the active class is added to the element, the `active-add` and the `active-add-active` classes are added just before
332+
to signal AngularJS to fire off an animation. When removed, the `active-remove` and the `active-remove-active` classes
333+
are applied to the element which in turn trigger another animation.
334+
335+
You may be thinking that we're just going to create another CSS-enabled animation.
336+
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled animations with the `animation()` module method.
337+
338+
__`app/js/animations.js`.__
339+
<pre>
340+
angular.module('phonecatAnimations', ['ngAnimate'])
341+
342+
.animation('.phone', function() {
343+
return {
344+
addClass : function(element, className, done) {
345+
if(className != 'active') {
346+
return;
347+
}
348+
element.css({
349+
position: 'absolute',
350+
top: 500,
351+
left: 0,
352+
display: 'block'
353+
});
354+
jQuery(element).animate({
355+
top: 0
356+
}, done);
357+
358+
return function(cancel) {
359+
if(cancel) element.stop();
360+
};
361+
},
362+
removeClass : function(element, className, done) {
363+
if(className != 'active') return;
364+
element.css({
365+
position: 'absolute',
366+
left: 0,
367+
top: 0
368+
});
369+
jQuery(element).animate({
370+
top: -500
371+
}, done);
372+
373+
return function(cancel) {
374+
if(cancel) element.stop();
375+
};
376+
}
377+
};
378+
});
379+
</pre>
380+
381+
Note that we're using {@link http://jquery.com/ jQuery} to implement the animation. jQuery
382+
isn't required to do JavaScript animations with AngularJS, but we're going to use it because writing
383+
your own JavaScript animation library is beyond the scope of this tutorial. For more on
384+
`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}.
385+
386+
<div class="alert alert-error">
387+
<h4>Important:</h4>
388+
Be sure to use jQuery version `1.10.2`. AngularJS does not yet support jQuery `2.x`.
389+
</div>
390+
391+
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
392+
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
393+
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
394+
be fired with `element` passed in as a parameter to that callback. The last parameter passed in is the
395+
`done` callback function. The purpose of `done` is so you can let Angular know when the JavaScript
396+
animation has ended by calling it.
397+
398+
The `removeClass` callback works the same way, but instead gets triggered when a class is removed
399+
from the element.
400+
401+
Within your JavaScript callback, you create the animation by manipulating the DOM. In the code above,
402+
that's what the `element.css()` and the `element.animate()` are doing. The callback positions the next
403+
element with an offset of `500 pixels` and animates both the previous and the new items together by
404+
shifting each item up `500 pixels`. This results in a conveyor-belt like animation. After the `animate`
405+
function does its business, it calls `done`.
406+
407+
Notice that `addClass` and `removeClass` each return a function. This is an **optional** function that's
408+
called when the animation is cancelled (when another animation takes place on the same element)
409+
as well as when the animation has completed. A boolean parameter is passed into the function which
410+
lets the developer know if the animation was cancelled or not. This function can be used to
411+
do any cleanup necessary for when the animation finishes.
412+
413+
414+
# Summary
415+
416+
There you have it! Animations are in place. Hopefully this has shown you how you can improve
417+
your AngularJS web application to have an awesome layer of animated interactivity.
418+
419+
<ul doc-tutorial-nav="12"></ul>

‎docs/src/templates/js/docs.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ docsApp.directive.docTutorialNav = function(templateMerge) {
203203
'',
204204
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
205205
'step_05', 'step_06', 'step_07', 'step_08', 'step_09',
206-
'step_10', 'step_11', 'the_end'
206+
'step_10', 'step_11', 'step_12', 'the_end'
207207
];
208208
return {
209209
compile: function(element, attrs) {

0 commit comments

Comments
 (0)
This repository has been archived.