I've been working on a small canvas animation for ffconf 2015 and realised two important optimisations that I've missed out on the past.

The net result: no more humming fan on my laptop.

READER DISCOUNTSave $50 on terminal.training I've published 38 videos for new developers, designers, UX, UI, product owners and anyone who needs to conquer the command line today. $49 - only from this link

The result is this simple retro animation that will only last a few days in production, so I've included a tweaked version here:

JS Bin on jsbin.com

For the sake of brevity (and actually getting this post written under the usual several hours), I'm just going to talk about what I changed.

Pinning FPS

I knew that the "right" approach was to use requestAnimationFrame (rAF) for animation, but my problems historically is that the call rate of my update function was way, way too often. This can either cause my animation to appear to be too quick, or results in the CPU overheating.

One nice advantage of rAF for animation is that it will stop firing when the tab is out of focus (i.e. if you switch to another tab). Whereas setInterval not only doesn't hit the timing you want, but it'll keep firing, burning up battery.

TIL requestAnimationFrame passes in a high resolution timestamp to the callback.

Using the timestamp, we can get a delta of the last run, and if, and only if, the last frame was drawn X FPS ago, then we'll draw a new frame. For example:

var lastFrameTime = 0 ; function draw ( elapsedTime ) { var delta = elapsedTime - ( lastFrameTime || 0 ) ; window . requestAnimationFrame ( draw ) ; if ( lastFrameTime && delta < 33 ) { return ; } lastFrameTime = elapsedTime ; }

Minimise your paints

Originally my demo was drawing a number of squares that would scale towards the viewer giving the impression of movement. Initially innocuous:

function draw ( ) { ctx . beginPath ( ) ; ctx . moveTo ( x , y ) ; ctx . lineTo ( x , y + y2 ) ; ctx . lineTo ( x + x2 , y + y2 ) ; ctx . lineTo ( x + x2 , y ) ; ctx . stroke ( ) ; ctx . closePath ( ) ; } function update ( ) { for ( i = 0 ; i < boxes . length ; i ++ ) { boxes [ i ] . draw ( ) ; } }

This would be repeated for every "box" animating towards the viewer. Since I'm just drawing lines, I could batch all these together all in one go and group the collective shapes under one path, then run a single stroke:

function draw ( ) { ctx . moveTo ( x , y ) ; ctx . lineTo ( x , y + y2 ) ; ctx . lineTo ( x + x2 , y + y2 ) ; ctx . lineTo ( x + x2 , y ) ; } function update ( ) { ctx . beginPath ( ) ; for ( i = 0 ; i < boxes . length ; i ++ ) { boxes [ i ] . draw ( ) ; } ctx . stroke ( ) ; ctx . closePath ( ) ; }

It's a fairly tiny optimisation, but the result is the same, but with less interaction with the canvas, and given we're aiming to be in and out quickly, it's not a bad thing.

Single rAF handler

If you've got more than one animation running, you'll need to set-up multiple callbacks to requestAnimationFrame , but having multiple rAF callbacks isn't ideal, and starts to get unwieldy when you're working with others who also want to queue up their animations.

You really want everything to be handled in a single rAF handler.