Javascript Animation: Tutorial, Part 2
You're reading one of a three-part series on Javascript animation. Also see part 1: Javascript animation basics, and part 3: Creating motion tweens in javascript.
So You Want Smooth Animation, Eh?
In the first part of this tutorial, the basis of Javascript animation via iterative setTimeout()
or setInterval()
calls was discussed. This entry tries to explain why client-side animation is natively inefficient, and offers some suggestions for getting less-CPU-intensive animation effects.
CPU usage (and subsequently, frame rate) are affected by various things including the number of objects being moved, the efficiency of the animation code and the document's layout complexity - which ultimately determines the amount of work the browser has to do to render the view.
Since all text and no pictures is kind of boring, why not jump to the chase and view the demo. This uses a single timer for animation, which is explained later.
Web browsers weren't really developed with DOM-based animation (that is, using native HTML elements) in mind; repositioning and re-rendering an image or <div>
element within any document means that a certain amount of "reflow" has to happen, where the browser must not only re-draw but also do some calculations to determine if and how the repositioned element has affected the flow of nearby elements and the overall document. This logic is also seen in action when fluid sites dynamically rearrange themselves to fit one's browser window when being resized.
From my experience and findings over the years, the primary source of CPU drain stems from the browser's reflow/redaw response to dynamic changes in the DOM.
Want More? Make The Browser Work Less
With animation, the browser eats up CPU in responding to DOM changes made by script. Though sometimes difficult to measure and test, it also makes logical sense that additional browser-rendered visual attributes and effects will contribute to slowing down the rendering of a document and subsequent animation speed; background colours, images and borders in CSS or filters such as opacity are drawn by the browser, and thus require more work to generate dynamically. In addition, any scripted or native browser event that can cause redraw/reflow will ultimately eat up more CPU. By reducing the number of events generated in running an animation, you can spare more cycles for smoother motion.
Common Browser-Intensive Operations
Though by no means a definitive list, the following describes a number of DOM and layout methods which appear to be more "expensive" to render and thus affect animation, along with an explanation and suggested workarounds.
As an added note, these should be considered relevant primarily for the element(s) being animated. Non-animated elements do not generally burn as much CPU when being reflowed/rendered, but should not be disregarded entirely.
- Relative Positioning
-
The Good:
- Resizing of relative elements affects document flow (fluid layout)
-
The Bad:
- Easily very CPU-intensive if many elements are involved/layout is complex
-
The Workaround:
- Absolutely-position animated element containers (or single elements to be animated) wherever feasible. This will "isolate" the elements in question from the normal document flow, minimizing the amount of reflow calculation done by the browser.
- Browser-Rendered Visual Style - CSS
-
The Good:
- Content separate from presentation
- Visual style handled by CSS, can be easily re-used elsewhere
- Self-explanatory
-
The Bad:
- Opacity and background images (depending) can seriously degrade performance
-
The Workaround:
- Performance can be compared between PNGs and CSS opacity/filters; mileage may vary depending on circumstances
- CSS Class Swapping
-
someDiv.className = 'moving';
-
The Good:
- Good for a one-time "set and forget" change, eg. active vs. inactive
-
The Bad:
- Slow as hell, especially if every animation frame/loop involves a class name change
- Browser must re-evaluate CSS rules related to this class and child DOM elements of the targeted element, and other CSS rules which may apply, eg.
div.someClass, div.someClass img { background:#ff3333; }
-
The Workaround:
- Use script to apply visual style change directly to the element in question, avoiding class swapping altogether; eg.
someDiv.style.backgroundColor = '#ff3333';
. This may also apply to element ID swapping.
- Use script to apply visual style change directly to the element in question, avoiding class swapping altogether; eg.
- Background Images
-
#someDiv { background:transparent url(/some/image.gif) no-repeat 0px 0px; }
-
The Good:
- Can save an extra
<img />
element from being used. Position/tile attributes are easily adjusted.
- Can save an extra
-
The Bad:
- Redraw (animation) appears to cause "wait" cursor flicker under Internet Explorer; usually more CPU-intensive than using
<img />
. - Image cannot be stretched with this method.
- Redraw (animation) appears to cause "wait" cursor flicker under Internet Explorer; usually more CPU-intensive than using
-
The Workaround:
- Substitute with
<img />
where tile is not necessary or if stretching is desired. - If adjusting image positioning, use an absolutely-positioned image within a fixed-size, cropped container rather than background x/y.
- Substitute with
Looping: Single vs. Multiple vs. Fixed Timelines
Part 1 discussed setTimeout()
and setInterval()
-based animation, the two basic methods of creating a loop for moving objects at a specified time interval. ("Timer" will be used to refer to these calls.) When moving multiple objects, it may seem logical to use a separate timer for each object; however, it seems the best framerates are obtained from a single loop which iterates through and moves all active elements. Multiple timers seem to make animation less efficient and consistent, for whatever reason.
- Multiple
-
Not so good:
var timers = [ setInterval(animateStuff1,20), setInterval(animateStuff2,20) ]; // Two simultaneous timers
Multiple, separate timers are created which run on their own - effectively creating separate timelines. For whatever reason, efficiency drops considerably with this method.
- Single
-
Better:
var timer = setInterval(animateAllStuff,20); // One function which will loop and animate all items - better
A single timer is created which calls a function with a loop; this iterates through all items to be animated and moves them as necessary.
Single timer function (loop):function animateAllStuff() { for (var i=objectsToAnimate.length; i--;) { objectsToAnimate[i].move(); } }
In the case where animations may start and end on their own under a single timer (as above,) an object-oriented approach handles individual objects' separate states very nicely.
- Fixed
-
Alternate:
var timers = []; for (var i=0; i<10; i++) { timers[i] = function() { setTimeout(animateStuff,20*(i+1)); } } // Programmatically same as: setTimeout(animateStuff,20); setTimeout(animateStuff,40); setTimeout(animateStuff,60); ... setTimeout(animateStuff,200);
A loop creates ten timers simultaneously, each with a predetermined execution time (20, 40, 60, 80 msec from now.) This could be used in the case where animations are of fixed length (eg. 10 frames) or duration, and don't have a large frame count.
The benefit to this method is that the animation time will be fixed; if the browser can't keep up with the animation, frames will merely be dropped. The other methods will run more slowly and may take more time, but will not drop frames. However, the performance of the fixed method may begin to degrade quickly as the number of frames increases (since each frame results in a call to
setTimeout()
.)
Browser Differences
A common theme in client-side development, different browsers perform with varying efficiency when it comes to Javascript animation. In my experience, Opera 8 appears to be the currently-fastest browser in terms of animation regarding CPU vs. framerate; Internet Explorer on Windows and Mozilla's "Deer Park" (Firefox) Alpha are second and third, respectively, in terms of performance. Safari 2.0 is notably faster than its prior versions on Mac OSX, as well. Ultimately when developing and testing javascript animation routines, performance will vary widely depending on the application; the best measure of performance is a full build and comparison across the various browsers and platforms.
Part III will go into some more detailed animation routines.