Transform Rewrite: Dancing Trees and Errant Pixels


Two months later, I’ve finally had some time to finish a rewrite of my “CSS3″ transform/transformed element GWT module. I’m pretty pleased with its new state and don’t anticipate the need for any more major changes in the near future. It’s still definitely in beta, but I’m sufficiently confident in it that I’ve checked in the code, generated javadocs, written a sample app, and uploaded a zipped jar which includes all the aforementioned. Though checking out the code via Subversion doesn’t seem to have been too high a barrier before now for some, hopefully this will encourage others to take a peak.

It’s so bad

Once again, I needed to write a simple demo for a module, something obvious in effect, intent, and execution. I’m usually terrible at these—hemming and hawing over details irrelevant to the point of a piece—so I gave myself two hours to come up with something and code it. I didn’t quite make it (there was some hemming and hawing), but it was close.

I don’t remember exactly how some initial thoughts on nested spinning boxes lead to this, but I think it turned out pretty well, if a little crude. Conveniently, it ended up using the major parts of the library that most need demonstrating—creation, element transformation, and the use of standalone transforms—which I’ll talk about below. There was also only a single browser workaround needed: current versions of IE don’t support border-radius, so the shadows needed some slight repositioning.

But I’ll get to all that. Go look. Firefox ends up winning for rendering quality (and was used for most of these screenshots), but all the browsers do pretty well keeping up:

Demo: Transforming Trees

If it doesn’t really strike a chord, try running this in the background at the same time. Hey, I never said it was going to change someone’s life or scrub oil off the wetlands. Two (and a half) hours, remember?

Overview

The source for the demo is over in the usual place, but I’ll go through the important parts here, concentrating on the actual application of transforms.

Most everything happens in TreesWidget.java, except for this line in the gwt.xml module file:

<inherits name="gwt.ns.transformedelement.TransformedElement"/>

The project also needs to inherit from the Transforms module, but in this case TransformedElement has already done so.

Execution is divided up into initialization and an animation loop. The TransformedElements are created in the Widget’s constructor. In order to animate transforms efficiently in Internet Explorer, several data points about an element need to persist across frames, and so are kept in a TransformedElement object. See these posts for more about how this works. For a static transformation, the object can be discarded after creation; the transform will persist like any other style.

An update() method is then called from a timer, updating as often as it can.

Trees: TransformedElement

TreesWidget initializes the two tree DIV elements by wrapping them with TransformedElement objects:

bigTree = TransformedElement.wrap(bigTreeDiv);
littleTree = TransformedElement.wrap(littleTreeDiv);

As with all dancing trees, movement starts at the trunk, so the transform origin should be located there. The helpful folks writing and implementing the CSS3 transform spec have seen fit to give a built-in property to position the origin for just such a purpose. In this case, the origins need to remain in the middle of each tree, but moved to their base:

bigTree.setOriginPercentage(50, 100);
littleTree.setOriginPercentage(50, 100);

That’s all the initialization the trees need. In the animation loop, the trees sway back and forth while simultaneously crouching down and stretching up (is there a good antonym for crouching?). The parameters for each cycle are calculated and, since they are not cumulative, applied to the tree after its transform is reset to the identity.

public void update() {
  // calculate scaleX, scaleY, bigSkewAngle...
 
  bigTree.setToIdentityTransform();
  bigTree.scale(scaleX, scaleY);
  bigTree.skewX(bigSkewAngle);
 
  // animate littleTree
  // ...
}

That’s it.

Shadows: TransformedElement + Standalone Transform

A shadow can go a long way toward tying objects to each other and the background, giving a sense of physicality where there is none. Shadows often don’t have to be anywhere near realistic to fill that role, but in this case the limits of border-radius prevented me from creating simple blob shadows. Since the shadows were squished, the radii allowed by the browser were rather small, and so the shadows were less blobular and more rectangular.

Nothing a little over-engineering can’t solve!

The shadows used are actually exact style copies of the trees (except that, since all browsers that currently support browser-radius do so with obvious rendering cracks, expanded heights and widths are used in lieu of borders). The shadows are initialized just like the trees, since they too will transform about the trees’ bases.

bigShadow = TransformedElement.wrap(bigShadowDiv);
littleShadow = TransformedElement.wrap(littleShadowDiv);
 
bigShadow.setOriginPercentage(50, 100);
littleShadow.setOriginPercentage(50, 100);

Looks a bit like box-shadow.

Now for the trickery. We want a shadow at the base of the trees, coming toward the viewer at a slight angle, and squished to fit in the space available at the bottom (short because our trees do their best dancing at mid-day). That’s the more literal interpretation of the transform that is about to be applied. However—if you’re into this sort of thing—the transform can also be thought of as projecting the shape of the tree toward the viewer onto a slightly tilted plane.

The two descriptions are equivalent. I usually take any excuse I can get to play around with matrices for an afternoon, but in this case I only cared about the general effect, so I just flubbed around with the numbers until the scene looked right.

The shadows are going to dance with the trees, but the shadow positioning transform is never going to change. This gives a good excuse to create a canned transform that we can apply at any time. This saves a good bit of computation in each frame, but also allows the creation of this “shadow space” to be kept out of the animation method, where we don’t really care about its specifics any longer.

Like TransformedElements, Transforms are created from a factory method. This allows the library to delegate to native objects like WebKitCSSMatrix on platforms which support them. While the math will never come for free, some operations will be performed much more quickly in mobile browsers, for instance.

Transform shadowTransform = Transform.create();
 
// small translation so overlapping with trees
shadowTransform.translate(0, -10);
// angled
shadowTransform.skewX(-1.122);
// squished and toward the viewer (reflected)
shadowTransform.scale(1, -.12);

Finally, the transform and animation are applied to the shadows. At some point, everybody has difficulty with the order in which transforms should be applied. There are several ways to conceptualize the process: some like the idea that they are applied in “reverse order,” but I like to think of each transform as applied in the coordinate space created by the previous transformation.

In this case, we want to add the skew and scale animation to the shadows within the shadow space. If you’re a reverse-order kind of person, this means that the shadows are first skewed and scaled, just like the trees, and then projected onto the “ground.” It’s the same thing:

public void update() {
  // animate trees
 
  bigShadow.setTransform(shadowTransform);
  bigShadow.scale(scaleX, scaleY);
  bigShadow.skewX(bigSkewAngle);
 
  // animate littleShadow
}

The source is, again, all in the project repository, but here are the pertinent files. I’m linking to the versions as of this writing so things don’t get confusing; be sure to check if there are more recent revisions:

TransformTree.gwt.xml
TreesWidget.java
TreesWidget.ui.xml

Hall of Shame

I can’t resist comparing the rendering results between browsers. The actual transforms hold up well across platforms, which is nice, but border-radius results definitely leave something to be desired.

Internet Explorer remains kind of cute in its differently-abledness. WebKit and Opera are lagging Firefox in quality here, to varying degrees. Some of the disparity can be attributed to using a different radius for each corner, but gaps rear their ugly heads everywhere. This can be more than a little distracting when light colors are peaking around dark borders, or vice versa.

Please note that all of these screenshots come from a Windows machine. I hear that Snow Leopard’s Safari can fully hardware accelerate transforms, and I’m sure it also renders border-radius flawlessly and spends idle cycles scrubbing the wetlands*. Regardless, there are enough errant pixels here to drive anyone crazy.

Chrome 6.0.401.1 dev

Firefox 3.6.4

Internet Explorer 8

Internet Explorer 9 Platform Preview 2 (v1.9.7.7.66.6000)

Opera 10.53

Safari 4.0.5 (531.22.7)

*though I read in a forum post that Opera could do that before WebKit even filed a bug for it.

This entry was posted in Uncategorized and tagged , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">