Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Optimizing a Canvas Animation

DZone's Guide to

Optimizing a Canvas Animation

There are two important performance optimizations you might have missed in your HTML5 Canvas animation. Learn how to use a single rAF handler and more.

· Web Dev Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

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

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

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

Recognize the animation?  Try turning on your targeting computer ;)

For the sake of brevity (and actually getting this post written under the usualseveral 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 tooquick, 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) {
  // calculate the delta since the last frame
  var delta = elapsedTime - (lastFrameTime || 0);

  // queue up an rAF draw call
  window.requestAnimationFrame(draw);

  // if we *don't* already have a first frame, and the
  // delta is less than 33ms (30fps in this case) then
  // don't do anything and return
  if (lastFrameTime && delta < 33) {
    return;
  }
  // else we have a frame we want to draw at 30fps...

  // capture the last frame draw time so we can work out
  // a delta next time.
  lastFrameTime = elapsedTime;

  // now do the frame update and render work
  // ...
}


Minimize 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() {
  // ... calculate x, y, scale, etc
  // makes the shape: |_|
  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();
}

// update is called on a new frame
function update() {
  // ... update state then draw:
  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() {
  // ... calculate x, y, scale, etc
  // makes the shape: |_|
  ctx.moveTo(x, y);
  ctx.lineTo(x, y + y2);
  ctx.lineTo(x + x2, y + y2);
  ctx.lineTo(x + x2, y);
}

// update is called on a new frame
function update() {
  // ... update state then draw:
  ctx.beginPath();
  for (i = 0; i < boxes.length; i++) {
    boxes[i].draw();
  }
  ctx.stroke();
  ctx.closePath();
}


It's a fairly tiny optimization, 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.

I've written a small gist called raf.js that allows me to put all my rAF calls through a single handler (and added some niceties like the FPS pinning and a runningboolean flag).

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:
html5 ,canvas ,web dev ,animation

Published at DZone with permission of Remy Sharp, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}