Introduction
Animation is becoming a first-class citizen of the good user interface. Users loves animation. If the computer has the ability to just blow-out the answer, user is not accustomed to controls just popping-up. The visual ability of the human eye is based on frames and it is friendly for the web user interface to use animations.
Latest browsers include quite simple, but powerful interface for doing animations. This tip contains the basic idea (and code) for extending the interface to such powerful enough for using it in web application controls. The result interface is barely around 150 lines and contains facilities for events, storing data and of course, starting and stopping animation. It is compatible with Internet Explorer, Opera, Safari, Chrome and Firefox. Since the simplicity of the code, this tip stops at ideas in the code, not in the code itself.
Background
The general idea of the creating animation is using timer. Just start a timer, when it fires - paint. When done painting - start a timer for the next frame. Simple and useful, as long as it is done in JavaScript.
JavaScript
Why JavaScript handles the things with timers better? Multithreaded programming is really hard - useful, but hard. Things are a lot easier if there is one thread only. JavaScript thread that runs in browser just wakes up, does some script and goes to sleep. Last one is known as idle state. While executing browser tends to become non-responsive (even in chrome, where browser interface responds, website does not). Therefore browser script or responsive sites are so short that they are "invisible".
Even with one thread is possible to achieve simulated multithreading based on interruption. But if the task that interrupts change something that the interrupted task depends, there is no way to tell when that will happen. Therefore without external influence, the program will be different every time it is executed - that is not good. Therefore JavaScript is single-threaded uninterruptible language.
What about JavaScript timers. Let's take a look at some actual code.
(function() {
var complete = false;
setTimeout(function() {
complete = true;
}, 1000);
while(!complete);
})();
In this code, we set a variable to false
, start a timer with 1 second delay and begin an endless loop. When the timer fires, the variable will become true and end the endless loop. The developer not familiar with JavaScript timers may expect the browser to freeze for 1 second. It actually freezes permanently. This is because the code never enters an idle state and without the idle state, timers never fire.
The Code
The New Interface
The target of the code is to provide a usable interface for performing animations. The first of all is to make connection with the environment provided by browsers. For this, the code creates a requestFrame
and cancelFrame
functions. They use requestAnimationFrame
and cancelAnimationFrame
if it is provided by browser or just plain old timer if it is not. The provided by browser interface acts as a simple timer, but does much more. The animation frame in both cases (with the new interface or as a timer), fires when the JavaScript enters idle state. However with new interface, browser repaints the areas of the website when frame function completes. With the new interface, the timer may not start immediately after entering idle state to reduce the frame rate of animation. With plain old timers no frame rate is reduced, but browser may not generate paint event after every call of the timer function.
Multiple Animations
Every call to requestAnimationFrame
will most likely create a queue. Therefore for two animations, one will be called the first time, and the other - the second time. This will reduce the frame rate of animation by two. It is unnecessary for animation to paint only one element, therefore the interface in this code combines all requested frame function to be called in a single frame function provided to the browser. This effectively ensures frame rate to be equal to provided.
Memory Problem with Multiple Animations
It is important for web application to be stable at memory. JavaScript will collect every object and value that is not connected with variable. However due to closure variable may remain after context of the function is collected. JavaScript provides no way of knowing when the object has been detached from a certain place. To provide all animations in a single function, we must take the instance of Animation
function and store it in our global frame function. This will prevent collecting the animation object when it is no longer used. Provided solution is to hold the instance to the animation
object of all currently running animations. When animation is not running (and it will eventually stop), animation object is removed from the array of active animations and if there are no other attachments, it will be free. However if object has been detached from everywhere, it will still continue its animation until the stop()
is called or the animation completes.
Animation Event System
Another important point of JavaScript is its ability to be asynchronous. When something happens interested in that action will be notified. Therefore it is good idea to have events when animation starts and ends. Due to the fact frame goes too quickly, it is not a good idea to have anything else called for frame than the frame callback function. Frame callback function should modify the DOM/CSS as quickly as it can based on the progress of animation and then returns.
Animation Timing Effects
The progress that is not linear can be calculated from linear progress. For example for "swing" animation position of animation can be calculated as follows:
position = 0.5 - Math.cos( position * Math.PI ) / 2;
The original position is based on (currentTime - startTime) / (endTime - startTime)
which gives value between 0
and 1
. For values greater than 1
, callback function must be called with position = 1
to generate the final frame, then animation should stop itself.
Comparison with jQuery
Animation
class does not provide effects like jQuery, but also it doesn't mess with the design of the website. This gives fairly easy to use base to stop effects in the middle and start them again with different values while predefined effects in jQuery can be stopped in the middle, but cannot be started from middle state without resulting invalid position. The jQuery does not use the new animation function, therefore animation with jQuery is not (always) smoother like with provided function. Due to the fact plain old timer method uses 0ms as timeout, the frame rate is as fast as the browser can do and animation becomes even smoother.
Points of Interest
jQuery provides richer facility for premade effects. While there is a problem handling stopped effects, handling proper contents when animated element is wrapped and other annoying stuff, it is quite a useful place to start learning animations.