Nerdworks logo "The nerd shall inherit the earth."

Nerdworks Blogorama

Nerdspeak

Animating CSS3 2D Transforms
Technobabble
6/13/2011 5:01:35 AM  

There was a little “poke fun at the boss” mail trail at work recently (yep, we do that sometimes and everybody just takes it in stride; it just so happened that boss was the target this time but it might just as well have been yours truly or anybody else) and I contributed to the joint exercise with a little video cooked up with Windows Live Movie Maker (nope, I am not posting the video here!). Somebody wondered if that video could have been built with a HTML5, JavaScript and CSS3 solution and I figured I’d give it a shot.

The basic idea was to show a sequence of “scenes” where each scene animates one or more HTML elements in some fashion. My first choice was to consider using CSS3 transitions but given that I wanted this to work in Internet Explorer 9 (IE9) which does not support CSS3 transitions, I decided to go with a JavaScript based animation approach.

Being an ardent jQuery user I thought I’d extend jQuery’s effects system (primarily via the animate function) by writing a little plugin that would let me animate CSS3 2D transform matrices. I couldn’t use jQuery.animate as is because it requires the property being animated to be a numeric value (width, opacity, left, top etc.) and CSS3 2D transform values are essentially 3x2 matrices.

So what are CSS3 2D Transforms?

Before we talk about the plugin though let’s quickly review how CSS3 2D transforms work. Here’s a div for example that has been rotated clock-wise about its center by 45 degrees (you’ll need to use IE9 or a recent version of Firefox, Chrome, Safari or Opera to see the rotation):

:-)

The rotation was achieved by a bit of CSS3 code like so:

transform: rotate(45deg);

Since the CSS3 2D transform spec is a working draft standard at the time of this writing, all browsers currently provide support for this only through vendor prefixed CSS3 attributes. So to make this work today you’d have to actually write all of the following:

-o-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);

The vendor prefixes given above are for Opera, Firefox (Mozilla), WebKit based browsers (Chrome and Safari) and IE – in that order. Note that I have supplied the standard name for the attribute – “transform” - last in the list. This has been deliberately done so that when these browsers do start supporting the standard name for the attribute, that behavior (i.e., the standard behavior) will take precedence over the vendor prefixed versions by virtue of it showing up last in the rule.

Apart from rotation, the spec also supports the specification of translation, scaling and skew transforms. Multiple transforms can be applied to a single element at the same time by separating each transform by a space. Here’s another div that has been rotated about its center by 45 degrees and skewed along the X axis by 15 degrees.

:-)

And here're the CSS3 rules that produced this output:

-o-transform: rotate(45deg) skewX(15deg);
-moz-transform: rotate(45deg) skewX(15deg);
-webkit-transform: rotate(45deg) skewX(15deg);
-ms-transform: rotate(45deg) skewX(15deg);
transform: rotate(45deg) skewX(15deg);

Reading transform data back from the element

Now that we have the basic transform applied, let’s see what we get back if we query the transform via jQuery. We use jQuery’s css function to retrieve the value. On IE9 for instance here’s the code you’d use to get the transform:

var transform = $("div").css("msTransform");

And here’s the value IE9 returns for the second div above (the one which has both a rotate and a skew applied):

matrix(0.707107, 0.707107, -0.517638, 0.896575, 0, 0)

As you can tell, that doesn’t look anything like the way we specified the transform. It turns out that the browser is free to convert our transform specification into a matrix representation and discard the original string given in the style sheet. As you might expect we can also specify the transform in this format, should we choose to do so, instead of using the the syntax we used earlier. While we are reviewing matrices, it might be useful to consider the following matrix – known as the identity matrix. This is a transform matrix applying which produces basically no change in the source matrix – similar to the effect that multiplying a number by one produces on it.

matrix(1, 0, 0, 1, 0, 0)

Since we’ll need to work with these numbers individually, it might be useful to write a helper routine that can parse a string in this form and produce an array of 6 numbers.

(function ($) {
    //
    // regular expression for parsing out the matrix
    // components from the matrix string
    //
    var matrixRE = /\([0-9epx\.\, \t\-]+/gi;

    //
    // parses a matrix string of the form "matrix(n1,n2,n3,n4,n5,n6)" and
    // returns an array with the matrix components
    //
    var parseMatrix = function (val) {
        return val.match(matrixRE)[0].substr(1).split(",").map(function (s) {
            return parseFloat(s);
        });
    }
})(jQuery);

We basically use a regular expression to extract the relevant portion and convert it into an array of numbers. You’ll note that I’ve used the self-calling anonymous function technique above. This was done to prevent polluting the global namespace with variables such as matrixRE.

Another factor to consider while reading transform matrix values back from the DOM is the fact that browsers today implement CSS3 2D transforms through vendor prefixes. This means that code such as the following, where we use the standard name for the CSS attribute, simply won’t work!

var transform = $("div").css("transform");

We’re going to have to use the appropriate vendor prefix while setting and retrieving transform matrices from script. As this can quickly get quite cumbersome to do, it makes sense to wrap this activity up in a couple of utility routines. Here goes:

(function ($) {
    //
    // transform css property names with vendor prefixes; the plugin
    // will check for values in the order the names are listed here and
    // return as soon as there is a value; so listing the W3 std name for
    // the transform results in that being used if its available
    //
    var transformPropNames = [
        "transform",
        "msTransform",
        "-webkit-transform",
        "-moz-transform",
        "-o-transform"
    ];

    var getTransformMatrix = function (el) {
        //
        // iterate through the css3 identifiers till we
        // hit one that yields a value
        //
        var matrix = null;
        transformPropNames.some(function (prop) {
            matrix = el.css(prop);
            return (matrix !== null && matrix !== "");
        });

        //
        // if "none" then we supplant it with an identity matrix so that
        // our parsing code below doesn't break
        //
        matrix = (!matrix || matrix === "none") ? "matrix(1,0,0,1,0,0)" : matrix;
        return parseMatrix(matrix);
    };

    //
    // set the given matrix transform on the element; note that we apply the
    // css transforms in reverse order of how its given in "transformPropName"
    // to ensure that the std compliant prop name shows up last
    //
    var setTransformMatrix = function (el, matrix) {
        var m = "matrix(" + matrix.join(",") + ")";
        for (var i = transformPropNames.length - 1; i >= 0; --i) {
            el.css(transformPropNames[i], m);
        }
    };
})(jQuery);

With these functions handy, we can set and retrieve transforms like so and it should work well on all modern browsers:

setTransformMatrix($("div"), [0.707107, 0.707107, -0.517638, 0.896575, 0, 0]);
var matrix = getTransformMatrix($("div"));

The jQuery plugin

The jQuery plugin uses jQuery’s animation function to take advantage of its “easing” system. The animation is driven by having jQuery animate a phantom property called “percentAnim” from 0 through 100 on the element(s) in question and uses the “step” callback function to guide the actual animation of the transform matrix. Here’s the source of the jQuery plugin (assuming the functions defined above are available):

(function ($) {
    //
    // interpolates a value between a range given a percent
    //
    var interpolate = function (from, to, percent) {
        return from + ((to - from) * (percent / 100));
    }

    $.fn.transformAnimate = function (opt) {
        //
        // extend the options passed in by caller
        //
        var options = {
            transform: "matrix(1,0,0,1,0,0)"
        };
        $.extend(options, opt);

        //
        // initialize our custom property on the element
        // to track animation progress
        //
        this.css("percentAnim", 0);

        //
        // supplant "options.step" if it exists with our own
        // routine
        //
        var sourceTransform = getTransformMatrix(this);
        var targetTransform = parseMatrix(options.transform);
        options.step = function (percentAnim, fx) {
            //
            // compute the interpolated transform matrix for
            // the current animation progress
            //
            var $this = $(this);
            var matrix = sourceTransform.map(function (c, i) {
                return interpolate(c, targetTransform[i], percentAnim);
            });

            //
            // apply the new matrix
            //
            setTransformMatrix($this, matrix);

            //
            // invoke caller's version of "step" if one was supplied;
            //
            if (opt.step) {
                opt.step.apply(this, [matrix, fx]);
            }
        };

        //
        // animate!
        //
        return this.animate({ percentAnim: 100 }, options);
    };
})(jQuery);

There isn’t a whole lot going on there. The “step” function is called by jQuery every time a frame update is needed. It passes the current value of the property jQuery is animating over the given duration as an argument to the “step” function. In our case this will be a number that ranges between 0 and 100 and we take it as an indication of how far we are into the animation and use it to appropriately interpolate the values in the transform matrix. Here’s an example of how we might use this function to translate and rotate an element over 1 second:

var box = $("#box");
box.transformAnimate({
    transform: "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 150, 0)",
    duration: 1000
});

If you’re wondering how I got those numbers in the matrix, well, I used the JavaScript console that ships as a part of IE9’s debugging tools (just hit F12 if you’re running IE) and then manually applied the said transform and then retrieved the resulting matrix via jQuery! Not exactly rocket science, I know! Sarcastic smile

The jQuery plugin has been used to trigger an animation on the box shown below when you click on it. It uses the animation plugin defined above to translate the box 150 pixels along the X axis and also rotate it 135 degrees around the center in a clock-wise direction over 1 second and then does the reverse. Go ahead, give it a shot – try clicking the smiley!

:-)

Great, now make it rotate all the way 5 times!

At one point I needed to make a certain element rotate about 5 times around the center over a few seconds. So I figured I’ll just use my plugin to get it done. Turns out it isn’t quite as simple as that! The problem you see is that the plugin works purely by interpolating the values of the transform matrix from the source to the target matrix over the given duration. Interpolation does not imply that things will progress in a linear fashion. For example, imagine that I wish to rotate an element from 0 degrees through 270 degrees. One can achieve this by incrementing the rotation angle from 0 to 270. However, when represented as a transform matrix it turns out that we can just as easily achieve this by rotating counter-clockwise from 0 degrees to –90 degrees! Confused smile Not quite what we want! To make this kind of mindless animation work I had to put together a mini-animation framework (if you can call it that) myself.

First, I copied the setTransformMatrix function from the transform jQuery plugin:

var Utils = {
    //
    // utility for applying transform matrix
    //
    applyTransform: (function () {
        var transformPropNames = [
            "transform",
            "msTransform",
            "-webkit-transform",
            "-moz-transform",
            "-o-transform"
        ];

        return function (el, transform) {
            for (var i = transformPropNames.length - 1; i >= 0; --i) {
                el.css(transformPropNames[i], transform);
            }
        };
    } ())
};
}

And then implemented a simple animate routine whose job is to simply invoke a callback function at a predefined interval for a specified duration.

var Utils = {
    //
    // simple custom animation
    //
    animate: (function () {
        var ANIMATION_INTERVAL = 17;

        return function (duration, frame, done) {
            var start = Date.now();

            //
            // our animation routine
            //
            var anim = function () {
                //
                // compute animation progress
                //
                var elapsed = (Date.now() - start);
                var percent = elapsed / duration;

                //
                // invoke callback
                //
                frame(percent);

                //
                // schedule next frame update if need be
                //
                if (elapsed < duration) {
                    window.setTimeout(anim, ANIMATION_INTERVAL);
                } else {
                    done();
                }
            };

            //
            // schedule first frame update
            //
            window.setTimeout(anim, ANIMATION_INTERVAL);
        };
    } ())
};
}

Finally, I put together a routine to animate the transform by animating the transform options individually, i.e., you can for instance, translate along Y axis from 10 pixels to 200 pixels and skew along X axis from 20 degrees to 0 degrees and so forth. Here’s the function definition:

var Utils = {
    //
    // wrapper for animating transforms; "opt" might look like this:
    //  opt: {
    //      scale: {
    //          from: 0.01, to: 1.0
    //      },
    //      rotate: {
    //          from: 0.0, to: 360.0 * 2.0
    //      },
    //      translateX: {
    //          from: 10, to: 300
    //      }
    //  }
    //
    transformAnimate: (function () {
        var suffix = {
            scale: "",
            scaleX: "",
            scaleY: "",
            rotate: "deg",
            translate: "px",
            translateX: "px",
            translateY: "px",
            skew: "deg",
            skewX: "deg",
            skewY: "deg"
        };

        return function (duration, el, opt, done) {
            Utils.animate(duration, function (percent) {
                var transform = "";
                for (var p in opt) {
                    var val = opt[p].from + ((opt[p].to - opt[p].from) * percent);
                    transform += sprintf("%s(%f%s) ", p, val, suffix[p]);
                }
                Utils.applyTransform(el, transform);
            }, done);
        };
    } ())
};

If I wanted to implement the same animation I did earlier, i.e. translate the box 150 pixels along the X axis and also rotate it 135 degrees around the center in a clock-wise direction over 1 second using this new framework, then I’d do something like this:

Utils.transformAnimate(1000, $("#box1"), {
        translateX: { from: 0.0, to: 150.0 },
        rotate: { from: 0.0, to: 135.0 }
    }, function() {});

Here’s another box for you that you can click to make it rotate and translate a bit.

:-)
Pretty cool don’t you think?! Nerd smile
Link Comment (2)
 
Windows Live Writer + ATOM Publishing = Awesome!
Technobabble
6/12/2011 12:13:44 PM  

Ever since I embarked on this blogging expedition I have used a Windows Forms based homebrewed blog client to create my posts. I would hand code the HTML in Visual Studio or notepad++ and then copy/paste the markup into this tool which would call a web service hosted on my blog to create the post. This is what the console looks like:

Blogorama console screen shot

Well, this worked for a long time and has served its purpose but I have over time realized that the complexity of this process has subconsciously been a bit of a de-motivator. Being essentially lazy, I’d have to get really excited about a topic to overcome the pain of hand coding the HTML. Also, having to open and close tags constantly really doesn’t lend itself to a free translation of thought to typing. And finally, not having the services of a spell checker handy meant that many of my posts would go live with every typo in its designated place!

Well, not anymore! I finally got around to fixing my site up to support the publishing of blog posts via the excellent Windows Live Writer! For those of you who don’t know, Windows Live Writer (WLW) is a free desktop blog client from Microsoft that allows you to create/edit blog posts and is designed to work seamlessly with most of the popular blogging platforms out there. My blog being a custom written piece of work it wouldn’t just work out of the box with WLW. Fortunately, WLW supports a publishing protocol known as the Atom Publishing Protocol (AtomPub) which means that basically any site that implements AtomPub would be able to have its content managed via WLW.

Some kindly souls over at a company called JH Software have made an awesome ASP.NET library (for free!) that makes it somewhat trivial to provide support for AtomPub on your own sites. Long story short, I downloaded the library, fixed up my site by hooking up the appropriate sub classes (and oh, I took this opportunity to upgrade the .NET framework being used by the blog engine to 4.0 from 3.5 and the process went surprisingly smoothly I am happy to report) and with a minimal amount of hair pulling was able to get everything going just right (well, except for the category selection in WLW bit that I’ll have to come back to some other time).

This very post for instance was authored in WLW and everything worked just peachy! Here’s a work in progress screenshot of this post in WLW:

wlw

WLW is truly an incredibly smart piece of software. Not only is it able to handle embedding pictures, script, video etc. directly into posts – I think its best feature is its ability to automatically detect the “theme” being used on a blog and give you an almost exact WYSIWYG authoring experience. It feels like I am typing directly onto the blog. Sweet!

Link Comment
 
blogorama home
about this blog
email the author
where on earth am i?
subscribe to mailing list
feeds Use these links for feed syndication
rss  |  atom
by category
technobabble (60)
philosophical crud (3)
irrelevant stuff (7)
archive
november, 2011 (2)
october, 2011 (1)
september, 2011 (7)
july, 2011 (3)
june, 2011 (2)
may, 2011 (3)
april, 2011 (1)
march, 2011 (1)
february, 2011 (1)
february, 2010 (1)
october, 2009 (1)
september, 2009 (1)
july, 2009 (5)
march, 2009 (2)
august, 2008 (2)
march, 2008 (1)
january, 2008 (1)
september, 2007 (2)
april, 2007 (1)
february, 2007 (2)
december, 2006 (1)
october, 2006 (1)
september, 2006 (4)
august, 2006 (3)
july, 2006 (4)
june, 2006 (3)
may, 2006 (6)
april, 2006 (2)
recent entries
Implementing variab...
Debugging existing...
Screen scraping wit...
Building an Instagr...
Building an Instagr...
Organizing your Jav...
298051 hits