Animation can add a splash of character to an otherwise bland
application. This chapter systematically works through the animation
utilities that are built right into Base as well as the dojo.fx
(pronounced "effects") module that
Core provides. This chapter includes a lot of source code and the bulk
of the content builds upon only a few other concepts covered in earlier
chapters. As such, this chapter may prove useful as a near-standalone
reference.
The toolkit provides animation facilities in Base and
supplements them with additional functionality offered through
dojo.fx
. The stock functionality
offered by Base includes _Animation
, a class that acts as a delegate
in that it fires callbacks according to its configuration; these
callback functions are what manipulate properties of a node so that it
animates. Once instantiated, all that has to be done to execute an
_Animation
is to invoke its
play
method.
Warning
The leading underscore on the _Animation
class currently designates at
least two things:
The API isn't definitively final yet, although it is really stable and probably will not change much (if any) between version 1.1 of the toolkit and when it does become final.
You generally won't be creating an
_Animation
directly. Instead, you'll rely on auxiliary functions from Base anddojo.fx
to create, wrap, and manipulate them on your behalf. You will, however, usually need to run theirplay
methods to start them.
Before delving into some of the advanced aspects of
animations, let's kick things off with one of the simplest examples
possible: a simple square on the screen that fades out when you
click on it, shown in Figure 8-1. This example uses one
of the two fade
functions
included with Base. The fadeOut
function and its sibling fadeIn
function accept three keyword arguments, listed in Table 8-1. Figure 8-1 shows an illustration of
the default easing function.
Table 8-1. Parameters for Base's fade functions
Parameter | Type | Comment |
---|---|---|
| DOM Node | The node that will be faded. |
| Integer | How many milliseconds the fade should last. Default value is 350. |
| Function | A function that adjusts the acceleration and/or deceleration of the progress across a curve. Default value is:
Note that the easing
function is only defined from a domain of 0 to 1 for
|
The node and duration parameters should be familiar enough,
but the notion of an easing function might seem a bit foreign. In
short, an easing function is simply a function that controls the
rate of change for something—in this case an _Animation
. An easing function as simple
as function(x) { return x; }
is
linear: for each input value, the same output value is returned.
Thus, if you consider the domain for possible x
values to be decimal numbers between 0 and 1, you notice that the
function always returns the same value. When you plot the function,
it is simply a straight line of constant slope, as shown in Figure 8-2. The constant slope
guarantees that the animation is smooth and occurs at a constant
rate of change.
Figure 8-1. A visualization of the default easing function; an easing function is only defined from a scale of 0 to 1 for fadeIn and fadeOut
Example 8-1 demonstrates how to fade out a portion of the screen using the default parameters.
Example 8-1. Fading out a node
<html> <head> <title>Fun with Animation!</title> <style type="text/css"> @import "http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css"; .box { width : 200px; height : 200px; margin : 5px; background : blue; text-align : center; } </style> <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"> </script> <script type="text/javascript"> dojo.addOnLoad(function( ) { var box = dojo.byId("box"); dojo.connect(box, "onclick", function(evt) { var anim = dojo.fadeOut({node:box}); anim.play( ); }); }); </script> </head> <body> <div id="box" class="box">Fade Me Out</div> </body> </html>
To contrast the default behavior with a different easing
function, shown in Figure 8-3,
consider the following revision to the previous addOnLoad
block. Note how the default
easing function is a relative smooth increase from 0 to 1, while the
custom easing function delays almost all of the easing until the
very end. This example also uses the dot-operator to run the
play
method on the _Animation
instead of storing an explicit
reference, which is cleaner and more customary.
dojo.addOnLoad(function( ) { var box = dojo.byId("box"); dojo.connect(box, "onclick", function(evt) { var easingFunc = function(x) { return Math.pow(x,10); } dojo.fadeOut({ node:box, easing : easingFunc, duration : 3000 }).play( ); });});
Tip
The dojox.fx.easing
module contains a number of excellent easing functions. Check them
out if you find yourself in need of some creative
possibilities.
Given that simple fades are incredibly common, having them at
a distance of one function call away through Base is wonderful.
However, it won't be long before you'll start to wonder about what
kinds of other slick animations you can create with _Animation
.
Let's build on our current foundation by introducing the rest
of the animateProperty
function,
which accepts one or more of the configuration parameters shown in
Table 8-2 in the same manner
that fadeIn
and fadeOut
work.
Table 8-2. The animateProperty function
Replace the existing addOnLoad
function with this updated one
to test out animateProperty
. In
this particular case, the width of the node is being animated from
200px to 400px:
dojo.addOnLoad(function( ) { var box = dojo.byId("box"); dojo.connect(box, "onclick", function(evt) { dojo.animateProperty({ node : box, duration : 3000, properties : { width : {start : '200', end : '400'} } }).play( ); }); });
It is worthwhile to spend a few moments experimenting with the
animateProperty
function to get a
good feel for the kinds of creative things that you can make happen;
it is the foundation of most dojo.fx
animations and chances are that
you'll use it often to take care of routine
matters. It accepts virtually any CSS properties all through the
same unified interface. Example 8-2 illustrates that
animations adjust other inline screen content accordingly. Clicking
on the blue box causes it to expand in the x
and y dimensions, causing the red and green
boxes to adjust their position as needed.
Example 8-2. Expanding the dimensions of a node
<html> <head> <title>More Fun With Animation!</title> <style type="text/css"> @import "http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css"; .box { width : 200px; height : 200px; margin : 5px; text-align : center; } .blueBox { background : blue; float : left; } .redBox { background : red; float : left; } .greenBox { background : green; clear : left; } </style> <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"> </script> <script type="text/javascript"> dojo.addOnLoad(function( ) { var box = dojo.byId("box1"); dojo.connect(box, "onclick", function(evt) { dojo.animateProperty({ node : box, duration : 3000, properties : { height : {start : '200', end : '400'}, width : {start : '200', end : '400'} } }).play( ); }); }); </script> </head> <body> <div id="box1" class="box blueBox">Click Here</div> <div id="box2" class="box redBox"></div> <div id="box2" class="box greenBox"></div> </body> </html>
If some of the animateProperty
parameters still seem
foggy to you, the previous code example is a great place to spend
some time getting more familiar with the effect of various
parameters. For example, make the following change to the animateProperty
function to produce 10
discrete frames of progress instead of a more continuous-looking
animation (recall that the duration divided by the rate provides a
number of frames):
dojo.addOnLoad(function( ) { var box = dojo.byId("box1"); dojo.connect(box, "onclick", function(evt) { dojo.animateProperty({ node : box, duration : 10000, rate : 1000, properties : { height : {start : '200', end : '400'}, width : {start : '200', end : '400'} } }).play( ); }); });
Given that the default easing function being used is fairly smooth, take a moment to experiment with the effect that various more abrupt functions have on the animation. For example, the following adjustment uses a parabolic easing function, shown in Figure 8-4, in which the values increase in value at much larger intervals as you approach higher domain values, and the discrete effect over the 10 distinct frames should be apparent:
dojo.addOnLoad(function( ) {
var box = dojo.byId("box1");
dojo.connect(box, "onclick", function(evt) {
dojo.animateProperty({
node : box,
duration : 10000,
rate : 1000,
easing : function(x) { return x*x; },
properties : {
height : {start : '200', end : '400'},
width : {start : '200', end : '400'}
}
}).play( );
});
});
Although the examples so far have implied that easing functions are monotonic,[17] this need not be the case. For example, try adjusting the working example with an easing function that is not monotonic, shown in Figure 8-5, to see the effect:
dojo.addOnLoad(function( ) {
var box = dojo.byId("box1");
dojo.connect(box, "onclick", function(evt) {
dojo.animateProperty({
node : box,
duration : 10000,
easing : function(x) {return Math.pow(Math.sin(4*x),2);},
properties : {
height : {start : '200', end : '400'},
width : {start : '200', end : '400'}
}
}).play( );
});
});
Although you generally do not create raw _Animation
objects, you still have the
ability to control them for most of the common use cases. For
example, while an animation is ongoing, you have the ability to
pause, restart, and stop it prematurely, inquire about its status,
or cue it to a specific point. _Animation
provides methods for all of
these common tasks, listed in Table 8-3.
Table 8-3. _Animation control functions
Method | Parameters | Comment |
---|---|---|
|
| Stops an animation.
If |
| N/A | Pauses an animation. |
|
| Plays an animation,
optionally allowing for a delay (in milliseconds) before the
play operation. For paused animations, specifying |
| N/A | Returns the status of
an animation. Possible values for status are |
|
| Stops the animation
and then advances its percentage complete between 0.0 and
1.0. Setting |
Warning
Notice that gotoPercent
is not mixedCase
, like
goToPercent. This is one of
the few functions in the toolkit that does not use mixedCase
, which makes it very easy to
mistype.
You may also define any of the methods shown in Table 8-4 as an input to
animateProperty
. The following
table summarizes the functionality provided, and a block of code
follows that illustrates a change to animateProperty
that you can try to set
out the method calls.
Table 8-4. Input methods for animateProperty
Method | Parameters | Comment |
---|---|---|
| N/A | Fired before the
animation begins, providing access to the |
|
| Fires after the
animation has begun cycling, so in effect, this method is
somewhat asynchronous. The |
|
| Called for each discrete frame of the animation. The parameter is an object containing the current values for the style properties. |
| N/A | Called automatically when the animation ends. |
|
| Called each time
|
|
| Called each time
|
|
| Called each time
|
Here's a small code snippet you can use to tinker around with these methods firing:
dojo.animateProperty({ node : "box1", duration:10000, rate : 1000, beforeBegin:function( ){ console.log("beforeBegin: ", arguments); }, onBegin:function( ){ console.log("onBegin: ", arguments); }, onAnimate:function( ){ console.log("onAnimate: ", arguments); }, onEnd:function( ){ console.log("onEnd: ", arguments); }, onPlay:function( ){ console.log("onPlay: ", arguments); }, properties : {height : {start : "200", end : "400"} } }).play( );
The following adjustments to the working example illustrate
some basic methods for controlling an _Animation
:
<!-- snip --> <script type="text/javascript"> dojo.addOnLoad(function( ) { var box = dojo.byId("box1"); var anim; dojo.connect(box, "onclick", function(evt) { anim = dojo.animateProperty({ node : box, duration : 10000, rate : 1000, easing : function(x) { console.log(x); return x*x; }, properties : { height : {start : '200', end : '400'}, width : {start : '200', end : '400'} } }); anim.play( ); dojo.connect(dojo.byId("stop"), "onclick", function(evt) { anim.stop(true); console.log("status is ", anim.status( )); }); dojo.connect(dojo.byId("pause"), "onclick", function(evt) { anim.pause( ); console.log("status is ", anim.status( )); }); dojo.connect(dojo.byId("play"), "onclick", function(evt) { anim.play( ); console.log("status is ", anim.status( )); }); dojo.connect(dojo.byId("goTo50"), "onclick", function(evt) { anim.gotoPercent(0.5, true); console.log("advanced to 50%"); }); }); }); </script> </head> <body> <div> <button id="stop" style="margin : 5px">stop</button> <button id="pause" style="margin : 5px">pause</button> <button id="play" style="margin : 5px">play</button> <button id="goTo50" style="margin : 5px">50 percent</button> </div> <div id="box1" class="box blueBox">Click Here</div> <div id="box2" class="box redBox"></div> <div id="box2" class="box greenBox"></div> </body> </html>
[17] Basically, a function is monotonic if it moves steadily in one direction or the other, i.e., if it always increases or if it always decreases.
Get Dojo: The Definitive Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.