In preparation for my OSCON talk, I’ve been unearthing some of the fx enhancements that were added to Dojo’s gfx module back when version 1.1 landed. Some of these enhancements are pretty neat, and I thought they might make for a good excuse to get you started with a part of the toolkit you may not have ventured into quite yet.

Let’s get right to it with a document that demonstrates the fundamentals of how to create a gfx drawing context and place a shape somewhere on it:


<html>
    <head>
        <title>Fun with gfx!</title>

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="isDebug:true">
        </script>

        <!--
            Workaround necessary for loading gfx over the CDN until 1.2
            lands. See http://trac.dojotoolkit.org/ticket/4462
        -->
        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojox/gfx.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojox.gfx"); //maintain good form
            dojo.addOnLoad(function() {

                //create a 200x200 drawing surface
                var g = dojox.gfx.createSurface(dojo.byId("g"), 200, 200);

                //draw a circle on it at (100,100) with a radius of 10
                var circle = g.createCircle({cx : 100, cy : 100, r : 10})
                .setFill("blue");
            });
   </script>
    </head>
    <body>
    <div id="g"></div>
    </body>
</html>

As helpful background, you can browse the (mostly up to date) official gfx documentation to learn more about the gfx module in general. Overall, the gfx API is pretty solid and supports multiple back ends. Staying true to Dojo’s “develop once, deploy anywhere” philosophy, you can use the gfx API to get some work done and then not worry about whether the final drawing occurs in VML, SVG, Silverlight, etc. In the end, it’s mostly the same from an application standpoint.

Moving on to the fx part of this post, let’s say that we want to animate that circle to move from its current position at (100,100) over to (120,150) over a duration of 1 second. Here’s the updated code to make it happen; it uses the animateTransform function to get the job done by translating the the position of the content from one point to another over a duration:


            dojo.require("dojox.gfx");
            dojo.require("dojox.gfx.fx");
            dojo.addOnLoad(function() {

                //create a drawing surface
                var g = dojox.gfx.createSurface(dojo.byId("g"), 200, 200);

                //draw a circle on it
                var circle = g.createCircle({cx : 100, cy : 100, r : 10})
                .setFill("blue");

                //anim is a dojo._Animation
                var anim = dojox.gfx.fx.animateTransform({
                    shape : circle,
                    duration : 1000, //ms
                    transform: [
                        {name : "translate", start : [0, 0], end:[20,50]}
                    ]
                });

                anim.play(); 

            });

So far, so good. To take it a step further, let’s use dojo.connect to run another transform as soon as the first one ends. We’ll simply connect to the animation’s onEnd function to find out when the first animation ends to set that trigger. Note that because of the asynchronous behavior of an animation, we couldn’t have blindly called the two of them back to back. Here we go again:


            dojo.require("dojox.gfx");
            dojo.require("dojox.gfx.fx");
            dojo.addOnLoad(function() {

                var g = dojox.gfx.createSurface(dojo.byId("g"), 200, 200);

                var circle = g.createCircle({cx : 100, cy : 100, r : 10})
                .setFill("blue");

                var anim1 = dojox.gfx.fx.animateTransform({
                    shape : circle,
                    duration : 1000, //ms
                    transform: [
                        {name : "translate", start : [0, 0], end:[20,50]}
                    ]
                });

                var anim2 = dojox.gfx.fx.animateTransform({
                    shape : circle,
                    duration : 1000, //ms
                    transform: [
                        {name : "translate", start : [20, 50], end:[0,0]}
                    ]
                });

                dojo.connect(anim1, "onEnd", anim2, "play");

                //uncomment the following line for perpetual motion
               //dojo.connect(anim2, "onEnd", anim1, "play");

                anim1.play(); 

            });

Since the animateTransform function returns dojo._Animation objects, we could have alternatively required in the dojo.fx module and then used the dojo.fx.chain function instead of setting up a connection via something like dojo.fx.chain([anim1, anim2]).play().

Another neat feature in the gfx.fx module is the animateStroke function, which allows you to thicken or thin out the stroke in an animated way. Try out the following snippet to cause a “blip” like effect when the mouse hovers over the circle. We’ll achieve this effect by rapidly increasing and decreasing the overall appearance of the circle by varying its stroke width. Something like this might be great for giving the user a visual indication that demonstrates a hover just registered. This kind of signal might be good for communicating that it’s now appropriate to try and click the mouse for some other action to occur.


            dojo.require("dojox.gfx");
            dojo.require("dojox.gfx.fx");
            dojo.addOnLoad(function() {

                //create a drawing surface
                var g = dojox.gfx.createSurface(dojo.byId("g"), 200, 200);

                //draw a circle on it
                var circle = g.createCircle({cx : 100, cy : 100, r : 10})
                .setFill("blue");

                circle.connect("onmouseover", function() {

                    var w = dojo.attr(circle.getNode(), 'stroke-width');

                    anim1 = dojox.gfx.fx.animateStroke({
                        shape : circle,
                        color : {start : "blue", end : "blue"},
                        duration : 200,
                        width : {end : parseInt(w)*8}
                    });

                     anim2 = dojox.gfx.fx.animateStroke({
                        shape : circle,
                        color : {start : "blue", end : "blue"},
                        duration : 200,
                        width : {end : parseInt(w)}
                    });

                    dojo.connect(anim1, "onEnd", anim2, "play");
                    anim1.play();

                });

                //style and wire up a click handler
                dojo.style(circle.getNode(), "cursor", "pointer");
                circle.connect("onclick", function(evt) {console.log(evt);});

        });

Although the stroke widths could have just been hard coded into the animateStroke function, I thought it might be slightly more instructive to introduce the getNode function, which returns the raw node that is associated with a shape object.

Well, that’s about it for this time. Stay tuned for more Dojo Goodness, and be sure to check out my upcoming book, Dojo: The Definitive Guide. It should be printing and on shelves any day now.