Time-based Animation with HTML 5 Canvas and JavaScript
Dan Tello, Former Senior Front-End Developer
Article Category:
Posted on
DISCLAIMER: Let it be known that time-based animation is not at all tied exclusively to canvas
or JavaScript. It can be and should be applied to any frame-based programming and animation, and has been for decades. That said, the principle is foundational to animating and developing games with canvas
and JS, and it's a great place to start.
Time-based Animation
When animating objects with JS, it is important that objects animate at the same speed, without being affected by varying frame rates. In the early days of video games, the speed of some programs was dependent on the speed of the computer processor. An object would move across the screen a certain number of pixels every frame. If the processor could only run a game at 15 frames per second (fps), and an object is programmed to move 10 pixels to the left every frame, said object will move at 150 pixels per second (pps). But what if a different processor can run the game at 60fps? The object would move at 600pps! That's way too fast! "How can I fix this?", you ask?
Animate objects relative to time instead of frame-based increments.
The key to solving this issue in keeping track of the time passed since the last frame. I’ve been calling this the delta, meaning difference—(in this case, a difference in time). Once we have the delta, we can figure out the distance our object should move this frame by using the following formula:
var distance = speed * delta;
I'll define these terms again to be clear:
- Distance: The number of pixels to move this frame.
- Speed: The speed in pixels per second.
- Delta: The number of seconds passed since last frame.
Here's how Bullet Bill's x-position is calculated each frame.
var distance = APP.bullet.speed * APP.core.delta; APP.bullet.x = APP.bullet.x - distance;
It's pretty straightforward. Use the delta to find the distance to travel each frame and marvel at your fancy new time-based, frame-rate-independent speed.
Here's the pattern I use for this:
APP.core = { frame: function() { APP.core.setDelta(); APP.core.update(); APP.core.render(); APP.core.animationFrame = window.requestAnimationFrame(APP.core.frame); }, setDelta: function() { APP.core.now = Date.now(); APP.core.delta = (APP.core.now - APP.core.then) / 1000; // seconds since last frame APP.core.then = APP.core.now; }, update: function() { // Update values // var distance = 100 * APP.core.delta; // APP.thing.x += distance; }, render: function() { // Render updates to browser (draw to canvas, update css, etc.) } };
In a frame-based script with time-based animation, I usually call this the "core" or "engine". It's what keeps the code running.
APP.core.frame
is just that: one frame, in which everything that happens in the code gets kicked off.
APP.core.delta
gets the current frame's delta.
APP.core.update
updates the properties of your objects (like Bullet Bill's x-position) based on time passed and user input.
APP.core.render
renders the updates to the browser by redrawing to the canvas, updating css styles, etc.
The last thing APP.core.frame
does is recursively call itself with requestAnimationFrame
.
About requestAnimationFrame()
Instead of using setInterval
or a recursive setTimeout
to repeatedly call APP.core.frame
, I'm using the use the fitter, happier, more productive requestAnimationFrame()
method. This allows browsers to "optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation." (Paul Irish) For more details, Mr. Irish has a great post, which includes the polyfill you'll need to safely use this.
Not just for canvas.
Like I said, this pattern isn't just for canvas animations. You can apply this pattern to anything you can animate. Here is the same example using divs and css positioning instead of canvas. (Hit "Play" to start.)
I hope this helps you out with your next browser animation adventure. The applications are infinite. The only limit... is yourself.