The Rubber Ducks demo: CSS3/Webkit animations + touch events in MobileSafari

November 15, 2010

I’ve previously posted about CSS3 transitions. I now decided to make a more complete demo, while learning about a more advanced CSS animation technique, animation with keyframes.

See the Rubber Ducks demo. It probably only works on MobileSafari (iPad or iPhone).

If you don’t have MobileSafari, here’s what it looks like.

You put your finger on one of the green bars, and drag it to another green bar, to transfer rubber ducks from one box to another. As the transfer is in progress, you see the rubber ducks fly around with a neat animation.

You can use two fingers to rotate and zoom the boxes. Just put two fingers on a box and rotate/pinch. Just make sure that at least one finger is on the white/background part of the box. (This can be improved.)

Following is a set of notes about this demo. You can of course get the source and read the comments, I commented the Javascript quite heavily.

Technology demo

As I say in the readme, the demo uses the following techniques:

Some of these things are basic, and some of these are new to me and I had not done them before. Overall, I’m happy with the small amount of code there is to enable it all, it’s quite compact and for the most part feels nice. It gives you a lot of “bang per line of code,” much more than if you were to hand-roll all this or even build the same stuff in a more complex language in a native app.

RaphaelJS and performance

My main purpose of doing this demo was to look at the state of the art of CSS3+touch and evaluate how this demo performs on devices. I actually did a very similar one a few months ago with RaphaelJS, a great SVG library. Functionally it was equivalent, but it performed terribly on devices, because it implemented all the calculation in software in Javascript, which does not take advantage of iOS hardware acceleration capabilities. So while it did the same thing, it was visibly jerky and close to unusable.

With Webkit, a lot of the legwork seems to be hardware-accelerated on devices, and this demo performs very well. It’s not without limits, though, and I did run into performance problems while poking a few things. For example, when you first put finger on a box, you’ll see a green border subtle glow/flashing, which I currently implemented with border style. I tried a similar one with box-shadow, and it worked fine on narrower boxes, but killed performance on wider boxes, as the devices don’t seem to be fast enough to keep recalculating dynamic box-shadows without visible slowdown.

Box-shadow aside, you’ll see that the performance is great in MobileSafari even when doing more complex operations, e.g all the ducks flying around on the screen and fading out at the end of their animation, which yields a fairly complex compositing operation.

The one thing that is in RaphaelJS, that is NOT there in Webkit/CSS as far as I can tell, is path-based animation. In RaphaelJS, I can construct all sorts of crazy Bezier paths and animate objects across them. In Webkit, I think I can only set points/locations, and all transitions are linear from pointA to pointB in a straight line, with no opportunity for curves. You can see in the demo that although the animation is pretty neat, it would be smoother with a curve.

Dynamic keyframe creation

This was one of my pain points when building the demo. I had to resort to a technique that I found on StackOverflow where you basically just keep inserting a CSS rule for each new animation. This works, but it’s not nice. The thread links to a Webkit commit where they demonstrate a technique to dynamically modify a keyframe animation without adding new CSS rules, but it’s more verbose than I’m interested in, I determined that the new rule insertion works better for me for now.

elementFromPoint and touch event model

Touch events work differently from mouse events. There is no mouseenter/mouseleave, for example—all touch events are passed to the original target. What I ended up doing was to bind a touchmove event to the parent container and track targets myself with elementFromPoint, which is a key API to power this kind of demo. I even don’t know how I would do it if elementFromPoint didn’t exist.

(Interestingly, jQuery event binding does not maintain the “touches” properties of touch events, so I have to use DOM addEventListener API instead of jQuery to bind the touch events.)

As ppk notes:

elementFromPoint() returns the topmost element located at the requested coordinates. / In a future version it should return an array of elements located at the coordinates, in (reversed?) order of z-index. Thus we’d get all the eligible elements, and it would be up to us to choose between them. Pretty please, browser vendors?

Yes. Please :) Since it right now only returns topmost element and you’ll see in my Javascript that I must do a trick to work around this.

Since it’s probably not great to change an existing API, just add a new one like elementsFromPoint (note “s”), which returns an array.