Adam Kempa | @adamkempa | Senior Software Engineer

May 15, 2012

The Legend of Real-World, Cross-Browser, Cross-Device Composited Animation

We here in the basement of Enlighten’s engineering labs were recently tasked with a unique challenge: to create an engaging animation composited from a user’s Facebook photos—on a cross-browser, cross-device basis. The fine print of this challenge also included the following requirements:

  •  Don’t make the user wait around – playback should feel almost instantaneous.
  •  Target the fastest, smoothest performance possible across desktop/laptop, tablets and other mobile devices.
  •  Stick to a single code base as much as possible, to streamline maintenance.
  •  Support some ancient browsers.
  •  Launch it really, really soon.

I know what you’re thinking: such solutions only exist in two places:

  1. In the wildest dreams of implementation engineers
  2. In pitch meetings that the tech lead was “invited to, I swear – I don’t know what happened. It must be something with Outlook. Anyway, I know this is an aggressive schedule….”

Naturally, we sized up the challenge before us, didn’t complain at all, and dove right in – and I’m here to tell you that we found the solution. And by “found” I mean: we kind of saw it once, walking through a clearing, and then it ran away:

Much like the guy who shot the above film (Roger Patterson, in case you’re wondering), I will happily tell you all about the hunt – and not just because I can see that you want to read several pages worth of sardonically documented engineering decisions. NAY, I will tell you… out of necessity.

You see, I have one final bigfoot metaphor prepared: While we’ve come out the other side of this experience with the memories of what we saw, we are unfortunately left with a complete lack of physical evidence. We’d love to point you to the site so that you could inspect the result, but we’re in one of those John Hughes-ian, “Yes, we’re dating but you can’t tell anyone we’re dating” situations. All we have to show for our adventure are a bunch of metaphorical plaster casts of oversized tracks.

But you believe me, right?

 

Instantaneous, composited animation

Ok, so let’s take this one step at a time. Requirement one: After a user authenticates via Facebook connect, we need to pull images from her account and instantaneously display them composited into an animation of some sort.

Serving up a rendered video file would certainly simplify delivery across all of our targets – mobile-accessible video delivery is something we do on nearly every project – but the “instantaneous” requirement means that we’re not going to be able to take the user’s photos and render them into unique video files on the server.

Fair enough. If we’re not serving up a video file, we’re talking about some sort of animation, and there are actually several options:

  • Flash
  • HTML5 Canvas element
  • Manipulating standard DOM elements with JavaScript
  • CSS3 Animation

For this project, Flash was off the table. We wanted to avoid using it in order to preserve iOS compatibility. Based on the schedule and the wide range of target platforms, we had a pretty strong feeling we’d be using some flavor of HTML5 or CSS3.

We created a series of test pages, each using one of the remaining methods listed above to perform the background animation for the final piece. Using these as a basis for comparison, we attempted to gauge performance across a wide range of our targets.

It immediately became clear that CSS3 animations were the best option for iOS (and that standard DOM manipulation was the worst). But the “single code base” requirement drove us toward selecting the HTML5 Canvas element, as it offered the most consistent performance across the widest range of our targets.

 

Browser support

Ok, cool, so we’re going with Canvas. Hm? Oh, right, browser support! Our first hurdle! This project needed to adhere to our existing matrix of supported browsers, which included the latest version of all webkit browsers (as well as FF3.6 for folks who love ignoring update messages), and Internet Explorer all the way back through IE7 (mercifully, IE6 was recently dropped). This list didn’t mesh with the Canvas-based solution we arrived at, as neither Internet Explorer 7 or 8 support the HTML5 Canvas element.

Enter: FlashCanvas.

FlashCanvas is a library that uses a SWF file and a whole mess of JavaScript to impersonate the HTML5 Canvas element within Microsoft Internet Explorer. It renders shapes and images via the Flash drawing API, supports almost all Canvas methods, and in many cases, runs faster than similar libraries which use VML or Silverlight.

What this meant was we were able to write the code to animate something using the HTML5 Canvas element, and then use that same code to control the animation in Internet Explorer 8 and older, where the Canvas element technically doesn’t exist.

Hooray! A single codebase, IE 7 and 8 supported! But not so fast, as there are a couple of caveats to using FlashCanvas as a fallback:

  • Since FlashCanvas displays things using Flash, all the fun cross-domain restrictions apply, so if you plan on pulling in images or files from other sources (as we pulled images from Facebook), you’ll need to set up a proxy page. The installation comes with a PHP proxy, but if you’re on a different platform, you’ll need to write your own.
  •  Custom font rendering isn’t built into the current release. If you dig around on the message boards, you’ll find works-in-progress that support this, but we found them to have a few issues. We ended up only using standard fonts when displaying using FlashCanvas.
  • Our particular animation was extremely image-intensive. Through trial and error over the course of the project, we discovered that a number of hiccups and visual artifacts we were seeing were caused by the first very frame of drawing a new image to the screen. We ended up drawing all images used throughout the entire course of the animation frame in every frame. If a particular image wasn’t used in the current frame, it was drawn just offscreen. All the visual defects went away. Your mileage may vary.

 

The right tool for the job?

At this point, we had internally arrived at a creative direction involving a background and several foreground layers scrolling horizontally in parallax. Our next step was to begin to prove this concept out using some of the leading “HTML5 Animation” tools. We created proofs of concept using each of the following:

At some level, all of these tools are GUIs that generate JavaScript to manipulate a Canvas element or other DOM elements. As a baseline, we also hand-coded an additional proof of concept. Based on our experiments, none of the high-level tools was producing code that performed acceptably on a cross-browser, cross-device basis (case in point: even the example projects that Adobe offers for Edge don’t work in IE9). Some were close, but, in the end, all of the experiments had at least one outstanding defect that disqualified it.

Meanwhile, our hand-coded native JavaScript POC was churning right along. So our decision was made. We were going to hand-code/animate everything! Quickly!

 

Why does it keep doing that thing?

Alright, let’s talk about animating in hand-coded Javascript!

Still reading? Ok, all the cool kids are definitely gone, we can relax.

The intricacies of timing animation frames in JavaScript are many, but, for the most part, they all boil down to optimizing the execution and the method of calling your frame function (helpful googling terms: “Game Loop” or “Tick Function,” depending on context).

In olden times, the setInterval function was used to repeatedly generate frames for an animation, but performance considerations could sabotage this method, making the resultant framerate vary wildly.

requestAnimationFrame is a newer API that specifically seeks to address JavaScript animation at a constant framerate. Unfortunately, cross-browser support is still a bit shaky. There were requestAnimationFrame polyfills evolving as we were coding, and we tried to keep up with them and test them against our codebase as we went.

Unfortunately, we were seeing collisions and “weirdness” that may or may not have been caused by requestAnimationFrame (Since the problems went away when we reverted away from it, it got the blame). Eventually the timeline won out and we stuck with that old standby setInterval to run the animation loop. Given more time, we’d definitely revisit this decision.

Since finishing this project, we’ve seen lots of discussion around the idea of javascript animation and framerate. Some good links to dig into:

 

Until next time?

Beyond investing more time into the requestAnimationFrame polyfill, what else would we do differently next time? I ‘d say the bulk of any additional time would be spent working on optimizing the bones of what we already have in place, with an eye towards playing nicely with the known behaviors of JavaScript garbage collection.

Some great articles on this sort of thing can be found here:

Until then: keep your eyes on the hills.