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.

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(  );
    });});

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.

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>

Core fx

The content of this chapter up to this point has concentrated entirely on the animation facilities that are provided by Base. The existing functionality in Base consisting of fadeIn, fadeOut, and animateProperty covers a tremendous amount of use cases; however, there are a few additional functions provided in Core's fx module that you can get for the cost of one additional dojo.require statement. These facilities for effects include functions for sliding nodes and wiping nodes in and out, as well as chaining, combining, and toggling animations.

Sliding

Sliding nodes is just as easy as fading them. You pass a hash containing configuration parameters to the dojo.fx.slideTo function just like you would with animateProperty. Table 8-5 summarizes.

Table 8-5. Parameters for Core's slide functions

Parameter

Type

Comment

node

DOM Node

The node that will be sliding.

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 the fadeIn and fadeOut.

left

Integer

Where the node's left corner should be at the end of the slide.

top

Integer

Where the node's top corner should be at the end of the slide.

Example 8-3 illustrates the sliding functions. The only portions of the page that are any different from the previous fade examples are emphasized.

Example 8-3. Sliding a node
<html>
    <head>
        <title>Animation Station</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.require("dojo.fx");

            dojo.addOnLoad(function(  ) {
                var box = dojo.byId("box");
                dojo.connect(box, "onclick", function(evt) {
                    dojo.fx.slideTo({

                        node:box,

                        top : "200",

                        left : "200"

                    }).play(  );
                });
            });
        </script>
    </head>
    <body>
        <div id="box" class="box">Slide Me</div>
   </body>
</html>

Wiping

Slides and fades are a lot of fun, but wipes are frequently used and have wonderful utility as well. The basic approach to using them should be no surprise by now. Most of the same arguments apply. Table 8-6 provides a synopsis.

Table 8-6. Parameters for Core's wipe functions

Parameter

Type

Comment

node

DOM Node

The node that will be wiped.

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 the fadeIn and fadeOut.

Be advised that in some layouts, border, margin, and padding values associated with nodes have been known to affect the layout once wipe animations have completed.

Following suit with the other examples in this chapter, Example 8-4 can get you started.

Example 8-4. Wiping a node
<html>
    <head>
        <title>Animation Station</title>
        <style type="text/css">
            @import "http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css";
            .box {
                width : 200px;
                height : 200px;
                text-align : center;
                float : left;
                position : absolute;
                margin : 5px;
            }
        </style>
        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>
        <script type="text/javascript">
            dojo.require("dojo.fx");

            dojo.addOnLoad(function(  ) {
                var box = dojo.byId("box");
                dojo.connect(box, "onclick", function(evt) {
                    dojo.fx.wipeOut({
                        node:box
                    }).play(  );
                });
            });
        </script>
    </head>
    <body>
        <div class="box">Now you don't</div>
        <div id="box" style="background : blue" class="box">Now you see me...</div>
   </body>
</html>

You may also find it especially interesting to experiment with custom easing functions for wipes. Try our custom, nonmonotonic easing function from earlier and note the interesting bouncy effect with the following addOnLoad change:

dojo.addOnLoad(function(  ) {
    var box = dojo.byId("box");
    dojo.connect(box, "onclick", function(evt) {
        dojo.fx.wipeOut({
            node:box,
            easing : function(x) { return Math.pow(Math.sin(4*x),2);   },
            duration : 5000
        }).play(  );
    });
});

Because the easing function increases, decreases, then decreases again, the internal _Animation that wipeOut uses scales the height of the node accordingly.

Chaining and Combining

There's something that's quite remarkable about watching an object slide, fade, and wipe around the screen, but that's not all you can do: you can use another function in Core fx, dojo.fx.chain, to chain together animations. This function is incredibly simple in that its only argument is an Array of _Animation objects and it returns another _Animation for you to play. Let's use it to make the box do something a little more fancy. Table 8-7 lists the functions for combining and chaining.

As of Dojo version 1.1, the animation functions chain and combine in this section have several known issues relating to how events such as beforeBegin and onEnd are processed when multiple animations are rolled up. The basic gist is that if you are trying to rely on these events for specific hooks in your application's logic, you might be better off using functions like dojo.connect and dojo.subscribe to rig up your own chains and combinations. Of course, for less advanced tasks, chain and combine work fine.

Table 8-7. Animation combination and chaining

Function

Comment

dojo.fx.chain(/* Array */ animations)

Chains together the animations enclosed in the array that is passed in as a parameter and returns a consolidated animation that you can play as usual. The resulting animation is the sequential result of playing each animation back to back.

dojo.fx.combine(/* Array */ animations)

Combines the animations enclosed in the array that is passed in as a parameter and returns a consolidated animation that you can play as usual. The resulting animation provides the effect of playing each of the original animations in parallel.

Example 8-5 demonstrates a box that makes a zigzag pattern across the screen. Note that you define custom easing function and other parameters just as normal.

Example 8-5. Chaining animations together
dojo.addOnLoad(function(  ) {
    var box = dojo.byId("box");
    dojo.connect(box, "onclick", function(evt) {
        var easing = function(x) { return x; };
        var a1 = dojo.fx.slideTo({
            node:box,
            easing : easing,
            duration : 1000,
            top : "150",
            left : "300"
        });
        var a2 = dojo.fx.slideTo({
            node:box,
            easing : easing,
            duration : 400,
            top : "20",
            left : "350"
        });
        var a3 = dojo.fx.slideTo({
            node:box,
            easing : easing,
            duration : 800,
            top : "350",
            left : "400"
        });
        dojo.fx.chain([a1,a2,a3]).play(  );
    });
});

But say you want to fade and slide at the same time. No problem. Following the same type API call as dojo.fx.chain, the dojo.fx.combine will do it in a jiffy. Any animations you pass into it through the Array parameter are run in parallel. First, let's look at a simple combination of our slide and fade examples. Example 8-6 shows the relevant change to addOnLoad.

Example 8-6. Combining animations
dojo.addOnLoad(function(  ) {
    var box = dojo.byId("box");
    dojo.connect(box, "onclick", function(evt) {
        var a1 = dojo.fx.slideTo({
            node:box,
            top : "150",
            left : "300"
        });
        var a2 = dojo.fadeOut({
            node:box
        });
        dojo.fx.combine([a1,a2]).play(  );
    });
});

It's easy to forget that slideTo is in dojo.fx while fadeIn and fadeOut are in Base, so take a moment to acknowledge that a call like dojo.fx.fadeIn would give you an error. If you do not issue a dojo.require("dojo.fx") before attempting to use anything in dojo.fx, you'll get an error.

Given that chain returns a single _Animation, let's try something more advanced (but still really simple) because it builds on the same fundamentals: in Example 8-7, we'll chain together several fade animations and combine them with several slide animations that we'll also chain together.

Example 8-7. Chaining and combining animations
dojo.addOnLoad(function(  ) {
    var box = dojo.byId("box");
    dojo.connect(box, "onclick", function(evt) {

        //chain together some slides
        var a1 = dojo.fx.slideTo({
            node:box,
            top : "150",
            left : "300"
        });
        var a2 = dojo.fx.slideTo({
            node:box,
            top : "20",
            left : "350"
        });
        var a3 = dojo.fx.slideTo({
            node:box,
            top : "350",
            left : "400"
        });
        var slides = dojo.fx.chain([a1,a2,a3]);

        //chain together some fades
        var a1 = dojo.fadeIn({
            node:box
        });
        var a2 = dojo.fadeOut({
            node:box
        });
        var a3 = dojo.fadeIn({
            node:box
        });
        var fades = dojo.fx.chain([a1,a2, a3]);

        //now combine the two chains together
        dojo.fx.combine([slides, fades]).play(  );

    });
});

Toggling

The dojo.fx.Toggler class is essentially a wrapper for configuring the animations for toggling (showing and hiding) a node. The class constructor accepts an associative array of parameters that include the show and hide functions as well as the durations for the show and hide functions. Toggler is nice in that there is very little thinking involved about what has to happen. You simply tell it what functions to use, provide the durations, and then manually call its show and hide function accordingly. Both the show and hide function optionally accept a parameter that delays the operation by a said amount of time (Table 8-8).

Table 8-8. Parameters for Core's Toggler function

Parameter

Type

Comment

node

DOM Node

The node to toggle.

showFunc

Function

A function that returns an _Animation for showing the node. Default value is dojo.fadeIn.

hideFunc

Function

A function that returns an _Animation for hiding the node. Default value is dojo.fadeOut.

showDuration

Integer

The duration in milliseconds to run showFunc. Default value is 200 (milliseconds).

hideDuration

Integer

The duration in milliseconds to run hideFunc. Default value is 200 (milliseconds).

Table 8-9 provides the method summary for the class.

Table 8-9. Toggler functions

Method

Comment

show(/*Integer*/delay)

Shows a node over a duration defined by showDuration using showFunc. The optional delay parameter causes the animation to wait by the specified amount before starting.

hide(/*Integer*/delay)

Hides a node over a duration defined by hideDuration using hideFunc. The optional delay parameter causes the animation to wait by the specified amount before starting.

Example 8-8 provides the compulsory source code and another modification to addOnLoad for our working example from Example 8-4.

Example 8-8. Toggling a node
dojo.addOnLoad(function(  ) {
    var box = dojo.byId("box");
    var t = new dojo.fx.Toggler({
        node : box,
        showDuration : 1000,
        hideDuration : 1000
    });
    var visible = true;
    dojo.connect(box, "onclick", function(evt) {
        if (visible)
            t.hide(  );
        else
            t.show(  );

        visible = !visible;
    });
});

If you try out the example, you should notice that clicking on the "Now you see me . . . " box causes it to fade out, while clicking on the "Now you don't" box causes the first box to fade back in.

Animation + Drag-and-Drop = Fun!

Drag-and-drop combined with animations are an incredibly powerful combination. Take a moment to review and experiment with the following block of code, which combines very basic concepts from drag-and-drop in the previous chapter with what you've been learning about in this one; it's illustrated in Figure 8-6.

A visualization of the x^5 easing function
Figure 8-6. A visualization of the x^5 easing function
<html>
    <head>
        <title>Animation + Drag and Drop = Fun!</title>

        <script
             type="text/javascript"
             src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>
    <script type="text/javascript">
        dojo.require("dojo.fx");
        dojo.require("dojo.dnd.move");
        dojo.addOnLoad(function(  ){
          var move = new dojo.dnd.Moveable(dojo.byId("ball"));
          var coords;
          dojo.subscribe("/dnd/move/start",function(e){
            // when drag starts, save the coords
            coords = dojo.coords(e.node);
          });

          //now use the coords to control where the image slides back
          dojo.subscribe("/dnd/move/stop",function(e){
            dojo.fx.slideTo({
              node: e.node,
              top: coords.t,
              left: coords.l,
              duration:1200,
              easing : function(x) { return Math.pow(x,5);}
            }).play(  );
          });
       });
    </script>
    </head>
    <body>
        <!-- Insert any image into the page here in place of ball.png -->
        <img style="position : absolute; left : 300px; top : 300px;"
                  id="ball"
               src="ball.png"/>
   </body>
</html>

To summarize, the code example detects the start of a global drag event and remembers the coordinates of where that drag began. Then, it waits until the drag event ends, and at that point, moves the image back to its original sport according to the specific easing function. The easing function dictates that the move back will be slow at first, but will rapidly accelerate toward the end in a trendy sort of way.

Colors

Animations and effects in a page may often depend on computing specific color values. Base provides an elementary Color class for encapsulating much of the mundane logic in computing colors, converting them to and from hexadecimal values, and so on. Several auxiliary functions for common operations on colors are also included.

Creating and Blending Colors

The Color class has a flexible constructor that can accept a named string value for a color, a hex string representing a color, or an array of RGB[18] values. Example 8-9 illustrates the creation of two Color objects and a function Base provides for blending colors.

Example 8-9. Blending Color objects
<html>
    <head>
        <title></title>
        <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 blue = new dojo.Color("#0000ff"); //could also have used "blue"

                var red = new dojo.Color([255, 0, 0]);

                var purple = dojo.blendColors(blue, red, 0.5);
                dojo.style("foo", "background", purple.toCss(  ));
            });
        </script>
    </head>
    <body>
        <div id="foo" style="width:200px; height:200px; padding:5px;"></div>
    </body>
</html>

The blendColors function accepted the red and blue Color objects and blended them according to a 50/50 mixture to produce the RGB value (128, 0, 128), a neutral shade of purple. The alternative to blending colors is to crunch the numbers yourself—not rocket science, but not very much fun either!

Table 8-10 summarizes the Color class provided by Base.

Table 8-10. Color functionality supported by Base

Method

Comment

Color(/* Array | String */color)

The constructor function, which accepts an array of RGB or RGBA values, or a String, which may be a named color like "blue" or a hex string like "#000ff". If no arguments are passed, the Color object is constructed with the RGBA tuple (255,255,255,1).

setColor(/* Array | String | Object */ color)

Works on an existing Color object to configure its value in a manner analogous to its constructor function; the preferred way of reusing an existing Color object.

toRgb( )

Returns a String value expressing a Color as an RGB value such as (128, 0, 128).

toRgba( )

Returns a String value expressing a Color as an RGBA value such as (128, 0, 128, 0.5).

toHex( )

Returns a String value expressing a Color as a hex value such as "#80080".

toCss(/* Boolean */ includeAlpha)

Returns a CSS-compliant String value expressing a Color as an RGB value such as (128, 0, 128). By default, includeAlpha is false. This method is the preferred means of transforming a Color object for use in styling a node.

toString

Returns a standardized String value for a Color as an RGBA value.

Most browsers currently implement a deviation of the CSS2 specification, which does not support RGBA tuples for expressing colors, so Color 's toCss( ) function (with no parameter passed in) is probably your choice method of deriving a value acceptable for passing into a method like dojo.style. If you need to express transparency for nodes that have color, use the style value for opacity to do so.

An example of how transparency works through the rgba( ) description for a color using Firefox 3
Figure 8-7. An example of how transparency works through the rgba( ) description for a color using Firefox 3

Transparency and opacity are the inverses of one another. If the opacity of something is 1.0, it is fully opaque, and its transparency is therefore 0.0. If its opacity were 0.1, it would be 90% transparent, and you would barely be able to see it.

Named Color Values Available Via Base

One other utility in Base is the configuration of preloaded named colors that is stored in dojo.Color.named, which maps named colors to RGB values. For example, to quickly access the RGB values for maroon, simply use the reference to dojo.Color.named.maroon to get back the RGB array [128,0,0]. Table 8-11 summarizes the named colors that are built into Base. Although you will probably want to use and manipulate Color objects directly, dojo.Color.named may still prove useful during development.

dojo.Color.named is not available from an actual Color object. It is a static collection of color values and no object creation is required to use it. Trying to access a color object instance's .named value will cause an error.

Table 8-11. Named color values available through Base

Name

Red

Green

Blue

black

0

0

0

silver

192

192

192

gray

128

128

128

white

255

255

255

maroon

128

0

0

red

255

0

0

purple

128

0

128

fuchsia

255

0

255

green

0

128

0

lime

0

255

0

olive

128

128

0

yellow

255

255

0

0

0

128

blue

0

0

255

teal

0

128

128

aqua

0

255

255

Additional Color Values Available Via Core

Although not included directly in Base, you can expand dojo.Color.named with more than 100 additional colors including all of the CSS3 named colors complete with SVG 1.0 variant spellings by performing a dojo.require("dojo.colors") statement (see Table 8-12). Note that you can also use the animateProperty function you learned about earlier to to animate the backgroundColor property. For example, you could provide start and end values of "black" and "white", "white" and "#43fab4", etc.

In addition to expanding dojo.Color.named, dojo.colors provides the additional enhancement of augmenting the Color constructor to accept HSL and HSLA color module formats. The HSL color space attempts to describe perceptual color relationships more accurately then RGB by representing colors in terms of hue, saturation, and lightness. You can read more about the CSS Color module at http://www.w3.org/TR/css3-iccprof.

Table 8-12. Additional named color values available through Core

Name

Red

Green

Blue

aliceblue

240

248

255

antiquewhite

250

235

215

aquamarine

127

255

212

azure

240

255

255

beige

245

245

220

bisque

255

228

196

blanchedalmond

255

235

205

blueviolet

138

43

226

brown

165

42

42

burlywood

222

184

135

cadetblue

95

158

160

chartreuse

127

255

0

chocolate

210

105

30

coral

255

127

80

cornflowerblue

100

149

237

cornsilk

255

248

220

crimson

220

20

60

cyan

0

255

255

darkblue

0

0

139

darkcyan

0

139

139

darkgoldenrod

184

134

11

darkgray

169

169

169

darkgreen

0

100

0

darkgrey

169

169

169

darkkhaki

189

183

107

darkmagenta

139

0

139

darkolivegreen

85

107

47

darkorange

255

140

0

darkorchid

153

50

204

darkred

139

0

0

darksalmon

233

150

122

darkseagreen

143

188

143

darkslateblue

72

61

139

darkslategray

47

79

79

darkslategrey

47

79

79

darkturquoise

0

206

209

darkviolet

148

0

211

deeppink

255

20

147

deepskyblue

0

191

255

dimgray

105

105

105

dimgrey

105

105

105

dodgerblue

30

144

255

firebrick

178

34

34

floralwhite

255

250

240

forestgreen

34

139

34

gainsboro

220

220

220

ghostwhite

248

248

255

gold

255

215

0

goldenrod

218

165

32

greenyellow

173

255

47

grey

128

128

128

honeydew

240

255

240

hotpink

255

105

180

indianred

205

92

92

indigo

75

0

130

ivory

255

255

240

khaki

240

230

140

lavender

230

230

250

lavenderblush

255

240

245

lawngreen

124

252

0

lemonchiffon

255

250

205

lightblue

173

216

230

lightcoral

240

128

128

lightcyan

224

255

255

lightgoldenrodyellow

250

250

210

lightgray

211

211

211

lightgreen

144

238

144

lightgrey

211

211

211

lightpink

255

182

193

lightsalmon

255

160

122

lightseagreen

32

178

170

lightskyblue

135

206

250

lightslategray

119

136

153

lightslategrey

119

136

153

lightsteelblue

176

196

222

lightyellow

255

255

224

limegreen

50

205

50

linen

250

240

230

magenta

255

0

255

mediumaquamarine

102

205

170

mediumblue

0

0

205

mediumorchid

186

85

211

mediumpurple

147

112

219

mediumseagreen

60

179

113

mediumslateblue

123

104

238

mediumspringgreen

0

250

154

mediumturquoise

72

209

204

mediumvioletred

199

21

133

midnightblue

25

25

112

mintcream

245

255

250

mistyrose

255

228

225

moccasin

255

228

181

255

222

173

oldlace

253

245

230

olivedrab

107

142

35

orange

255

165

0

orangered

255

69

0

orchid

218

112

214

palegoldenrod

238

232

170

palegreen

152

251

152

paleturquoise

175

238

238

palevioletred

219

112

147

papayawhip

255

239

213

peachpuff

255

218

185

peru

205

133

63

pink

255

192

203

plum

221

160

221

powderblue

176

224

230

rosybrown

188

143

143

royalblue

65

105

225

saddlebrown

139

69

19

salmon

250

128

114

sandybrown

244

164

96

seagreen

46

139

87

seashell

255

245

238

sienna

160

82

45

skyblue

135

206

235

slateblue

106

90

205

slategray

112

128

144

slategrey

112

128

144

snow

255

250

250

springgreen

0

255

127

steelblue

70

130

180

tan

210

180

140

thistle

216

191

216

tomato

255

99

71

transparent

0

0

0

turquoise

64

224

208

violet

238

130

238

wheat

245

222

179

whitesmoke

245

245

245

yellowgreen

154

205

50

Summary

This chapter has systematically walked you through Base and Core's tools for animation. A splash of animation, when applied with discretion, can really add that extra bit of umph that distinguishes your application from the rest of the crowd. After reading this chapter, you should:

  • Be able to use Base's utilities for fading nodes in and out

  • Be able to use Base's animateProperty function to animate arbitrary CSS properties

  • Understand the effect of easing functions, duration, and rate on an _Animation

  • Be aware of Core's facilities that supplement the animation support provided by Base

  • Be able to use Core's animation support for additional effects, including wipes and slides

  • Be able to chain together animations to run sequentially with dojo.fx.chain as well as run multiple animations in parallel with dojo.fx.combine

  • Be able to use dojo.fx.Toggler to hide and show a node via its simple, uniform interface

  • Understand how to combine animations with drag-and-drop to create highly interactive page content

  • Be able to create and effectively use Color objects to eliminate manual computation of color values in your code

    There are amazing graphics and animation tools backed by SVG, VML, and Silverlight backends, provided through the dojox.gfx module.

We're going to cover data abstraction in the next chapter.



[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.

[18] RGB is shorthand for "red green blue," one of the standard ways of representing colors in CSS. RGBA is shorthand for "red green blue alpha" and expresses a fourth color component, which represents the transparency of a color.