Chapter 8. Animation and Special Effects

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.

Animation

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 and dojo.fx to create, wrap, and manipulate them on your behalf. You will, however, usually need to run their play methods to start them.

Simple Fades

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

node

DOM Node

The node that will be faded.

duration

Integer

How many milliseconds the fade should last. Default value is 350.

easing

Function

A function that adjusts the acceleration and/or deceleration of the progress across a curve. Default value is:

(0.5 + ((Math.sin((n + 1.5) * Math.PI))/2).

Note that the easing function is only defined from a domain of 0 to 1 for fadeIn and fadeOut.

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.

A visualization of the default easing function; an easing function is only defined from a scale of 0 to 1 for fadeIn and fadeOut

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.

An simple easing function that is linear for values between 0 and 1

Figure 8-2. An simple easing function that is linear for values between 0 and 1

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.

An example of a custom easing function juxtaposed with the default easing function

Figure 8-3. An example of a custom easing function juxtaposed with the default easing function

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.

Animating Arbitrary CSS Properties

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

Parameter

Type

Comment

node

DOM Node | String

The node or a node id that will be animated.

duration

Integer

How many milliseconds the animation should last. Default value is 350.

easing

Function

A function that adjusts the acceleration and/or deceleration of the progress across a curve. Default value is (0.5 + ((Math.sin((n + 1.5) * Math.PI))/2).

repeat

Integer

How many times to repeat the _Animation. By default, this value is 0.

rate

Integer

The duration in milliseconds to wait before advancing to the next "frame". This parameter controls how frequently the _Animation is refreshed on a discrete basis. For example, a rate value of 1000 would imply a relative rate of 1 frame per second. Assuming a duration of 10000, this would result in 10 discrete updates being performed in the _Animation. By default, this value is 10.

delay

Integer

How long to wait before performing the animation after its play method is executed.

properties

Object

Specifies the CSS properties to animate, providing start values, end values, and units. The start and end values may be literal values or functions that can be used to derive computed values:

start (String)

The starting value for the property

end (String)

The starting value for the property

unit (String)

The type of units: px (the default), em, etc.

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(  );
    });
});
An example of a parabolic easing function

Figure 8-4. An example of a parabolic easing function

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(  );
    });
});
An easing function that increases and then decreases in value

Figure 8-5. An easing function that increases and then decreases in value

Programatically Controlling Animations

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

stop

/* Boolean */ goToEnd

Stops an animation. If goToEnd is true, then the _Animation advances to the end so that when play is invoked again, it will start from the beginning. goToEnd is false by default.

pause

N/A

Pauses an animation.

play

/* Integer */ delay

/* Boolean */ goToStart

Plays an animation, optionally allowing for a delay (in milliseconds) before the play operation. For paused animations, specifying true for goToStart restarts the animation versus continuing where it left off.

status

N/A

Returns the status of an animation. Possible values for status are "paused", "playing", and "stopped".

gotoPercent

/* Decimal */ percent

/* Boolean */ andPlay

Stops the animation and then advances its percentage complete between 0.0 and 1.0. Setting andPlay is true (false by default) restarts the animation.

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

beforeBegin

N/A

Fired before the animation begins, providing access to the _Animation and the node for modification immediately before anything happens.

onBegin

/* Object */ value

Fires after the animation has begun cycling, so in effect, this method is somewhat asynchronous. The value parameter is an object containing the current values for the style properties.

onAnimate

/* Object */ value

Called for each discrete frame of the animation. The parameter is an object containing the current values for the style properties.

onEnd

N/A

Called automatically when the animation ends.

onPlay

/* Object */ value

Called each time play is called (including the first time). The value parameter is an object containing the current values for the style properties.

onPause

/* Object */ value

Called each time pause is called. The value parameter is an object containing the current values for the style properties.

onStop

/* Object */ value

Called each time stop is called. The value parameter is an object containing the current values for the style properties.

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.