What happened to PlayZap?

A couple of years ago, I developed a HTML5 mobile game portal. Built with Node.js at the backend, and Ratchet.js (something like a precursor to Bootstrap, but specifically for mobile devices) on the front end, it was one of the first of it’s kind.

It was reasonably successful – successful in so far as it achieved the initial goals I set for it, but unsuccessful in that I couldn’t (or didn’t know how to) pivot and turn that success into a self-sustaining business.

In 2012, HTML5 games were in their infancy, and, being a web developer with a keen interest in games, I wanted to do something to push the technology – to show what it was capable of. I’m not going to pretend that my motivations were entirely altruistic – as a freelancer, growth in adoption of HTML5 meant that I could write games for clients instead of websites, which at that point I had been doing for years. The new tech was exciting, it had promise, all the right noises were being made, I wanted to work with it. I was going to work with it (I did work with it!), but it’s even better if you can get paid for it.

I decided to create a games portal, solely for HTML5 mobile games. Developers would be able to show off their games and profit by the adverts shown in them, and I’d have something to point to to prove how awesome HTML5 really was. I shipped PlayZap in early 2013.

After a slow launch, adoption quickly picked up as I spent a bit of time on marketing. I had thousands of users each month – both players and developers seemed to really enjoy the site. The stumbling block arose when I tried to think of how to turn it into a business. Server costs were negligible (I was hosting a dedicated instance on EC2), but I didn’t want to self-fund it forever.

As the tech was new, there wasn’t a business model as such to follow; but it was supposed that it would work in a similar way to how the Flash ecosystem used to work. Developers would integrate advertising APIs into their games, which were then (in lieu of an exclusive licence bought by a portal) shared widely on portals. It was a mutually beneficial relationship – portals provided the platform and the traffic, developers provided the games. The more eyeballs, the more gameplays, the more ad revenue for both the developer and the portal.

It didn’t quite pan out that way. In fact opposite happened – ad providers like Mochi, rather than pivoting, simply shut down.

There was – and, as far as I’m aware, this is still the case – a total lack of HTML5 ad providers that cater to mobile web games. Without providers like Mochi, early adopters turned to AdSense, on the assumption that, as HTML5 games are basically the same as web pages, they’d be able to use AdSense in their games. Some tried to use the square banners as interstitials, and many were banned for it. No one tried to fill the gap in the market, and the few that did have a suitable API placed such restrictions on their APIs’ use as to make implementation infeasible.

Without an interstitial ad provider on the horizon, all I had was banner ads that didn’t cover the bills.

In the end, PlayZap was very much a project without a business model. Others have pivoted to providing games for messaging apps, or used the partnerships they forged with developers to move into the mobile space. HTML5 has landed, but it found it’s footing elsewhere.

For me, the portal served it’s original purpose. I shuttered PlayZap earlier this year.

Do I regret doing it? Not at all. PlayZap started as a learning experience, and ended as a learning experience. I learned more about responsive user experience. I learned Node.js. I learned about hosting MongoDB in a production environment (hint: Monit is your friend). I learned that, if you want to turn something into a business, you need to know what your business model is *before* you build it. All valuable lessons.

Vector2(Success);

I set out on a mission to learn Unity perhaps a couple of years ago now. I ported Balloonz to Unity (using UnityScript), and wrote a shallow port of Jake Gordon’s Finite State Machine. It was a brief flirtation, cut short by the fact that I hated UnityScript with a passion. The problem for me was that I know browser-based JavaScript really well, and UnityScript is superficially similar syntax wise, so I’d get myself into trouble by assuming how things (like arrays for example) would behave.

BPACpV8CQAIM0uE

Recently, I decided to have another go at it – this time creating a Breakout clone using C#. And this second time around, I was already familiar with the Unity Editor, and had basic knowledge of how Unity’s Entity-Component-System-But-Not-Really works. I got the game working really quickly, and surprised myself at being able to create a level editor (by importing Tiled maps) with a trivial amount of effort.

tiled-breakout

Now I’m working on a point-and-click sci-fi adventure game – something original for a change. But I still remember how disheartened I felt that first time around.

sci-fi-thumbnails

The path to success is rarely a straight line. Nothing you ever learn is wasted – it might subconsciously inform your decisions, or directly inform your actions later. You can’t predict the future. Don’t be down on yourself if you don’t get it right the first time.

Unity Breakout Clone: Level Editor

I’ve not forgotten about the Breakout clone that I was working on. Truth be told, I haven’t looked at it in over a month.

Since the last post, I re-implemented the exploding brick type and the SFX that I lost, and created a level editor that imports .tmx level layouts from Tiled. I have a layer for brick colours, a layer for brick types (1hp, 2hp, multiball, indestructible bricks, etc), and a layer for powerups. There’s also an ‘extend paddle’ powerup.

I’d say this is somewhere in between Breakout and Arkanoid/Brickout in terms of features. I think I’ve learned everything that I set out to learn from it, so I’m going to shelve it for now.

Unity Live Training: Breakout

It’s funny how I started making games with a Breakout tutorial… and here I am, four years later, following a Breakout tutorial *sigh*. The more things change, the more things stay the same 🙂

It was easier than I expected it to be tbh, but I do have a *teensy little bit* of experience with Unity already (more on that later).

(Apologies for the slight stutter in the video – my Mac is old and tired :))

I took the results of the live training tutorial, and ran with them; adding an indestructible block, a block that spawns a multiball powerup, and better (but still ropey) paddle physics. Not bad for a few hours’ work!

I think I’m going to continue building on it. A level editor will be the next order of business I expect.

Web Audio API Tutorial

Welcome to this tutorial on the Web Audio API. I’ll show you how to load and play sounds, how to adjust the volume, how to loop sounds, and how to crossfade tracks in and out – everything you need to get started implementing audio into your HTML5 games.

Web Audio – a brief history

Before the release of the Web Audio API in 2011, the only cross-platform way of playing audio in the browser (without using Flash) was with the <audio> element. The <audio> element has a very basic feature set – there isn’t much to it beyond loading, playing, pausing and stopping a single track.

For the game developers taking advantage of the new and improved graphics APIs (WebGL, Canvas), audio support – or lack thereof – was a source of constant frustration. As graphics advanced, the paucity of the <audio> feature set became more pronounced. Worse still, <audio> was plagued by bugs across the different browser implementations, thwarting developers’ attempts to use the API for even the most basic of it’s intended purposes.

Ingenious hacks had to be devised – the ‘audio sprite’* was invented simply to get audio to work correctly in iOS. Developers clamoured for a better audio API to complement the rich, engaging visual experiences they were creating with the far-superior graphics APIs.

Enter, the Web Audio API.

The Web Audio API

The Web Audio API enables developers to create vibrant, immersive audio experiences in the browser. It provides a high-level abstraction for manipulating and controlling audio.

The API has a node-based architecture: a sound can be routed through several different types of nodes before reaching it’s end-point. Each node has it’s own unique purpose; there are nodes for generating, modifying, analysing and outputting sounds.

Where is it supported?

web-audio-browser-support
The Web Audio API is currently supported in all good browsers.

Test for API Support

Before you can load a sound, you first need to check whether the API is supported in your target browser. This snippet of code attempts to create an AudioContext.

var context;

try {
  // still needed for Safari
  window.AudioContext = window.AudioContext || window.webkitAudioContext;

  // create an AudioContext
  context = new AudioContext();
} catch(e) {
  // API not supported
  throw new Error('Web Audio API not supported.');
}

View demo

If the test fails, you have one of three options: a) ignore it (user gets no audio); b) use an <audio> sprite; c) use a Flash fallback. Personally, I’m in favour of option a) – as mentioned in the footnotes, audio sprites are a real pain in the backside to create, and Flash is a no-go in HTML5 mobile games.

Load a sound

Next, we’ll load a sound. The binary audio data is loaded into an ArrayBuffer via Ajax. In the onload callback, it’s decoded using the AudioContext’s decodeAudioData method. The decoded audio is then assigned to our sound variable.

var sound;

/**
 * Example 1: Load a sound
 * @param {String} src Url of the sound to be loaded.
 */

function loadSound(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';

  request.onload = function() {
    // request.response is encoded... so decode it now
    context.decodeAudioData(request.response, function(buffer) {
      sound = buffer;
    }, function(err) {
      throw new Error(err);
    });
  }

  request.send();
}
// loadSound('audio/BaseUnderAttack.mp3');

View demo

Testing file format support

To ensure that our audio is playable wherever the Web Audio API is supported, we’ll need to provide the browser with two variants of our audio source, in MP3 and Ogg format. This code snippet checks whether the browser can play Ogg format audio, and helps us to fall back to the MP3 where it’s not supported.

var format = '.' + (new Audio().canPlayType('audio/ogg') !== '' ? 'ogg' : 'mp3');
// loadSound('audio/baseUnderAttack' + format);

View demo

Play a sound

To play a sound, we need to take the AudioBuffer containing our sound, and use it to create an AudioBufferSourceNode. We then connect the AudioBufferSourceNode to the AudioContext’s destination and call the start() method to play it.

/**
 * Example 2: Play a sound
 * @param {Object} buffer AudioBuffer object - a loaded sound.
 */

function playSound(buffer) {
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
}
// playSound(sound);

View demo

Load multiple sounds

To load more than one sound, reference your sounds in a format that can be iterated over (like an array or an object).

var sounds = {
  laser : {
    src : 'audio/laser'
  },
  coin : {
    src : 'audio/coin'
  },
  explosion : {
    src : 'audio/explosion'
  }
};


/**
 * Example 3a: Modify loadSound fn to accept changed params
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function loadSoundObj(obj) {
  var request = new XMLHttpRequest();
  request.open('GET', obj.src + format, true);
  request.responseType = 'arraybuffer';

  request.onload = function() {
    // request.response is encoded... so decode it now
    context.decodeAudioData(request.response, function(buffer) {
      obj.buffer = buffer;
    }, function(err) {
      throw new Error(err);
    });
  }

  request.send();
}
// loadSoundObj({ src : 'audio/baseUnderAttack' });


/**
 * Example 3b: Function to loop through and load all sounds
 * @param {Object} obj List of sounds to loop through.
 */

function loadSounds(obj) {
  var len = obj.length, i;

  // iterate over sounds obj
  for (i in obj) {
    if (obj.hasOwnProperty(i)) {
      // load sound
      loadSoundObj(obj[i]);
    }
  }
}
// loadSounds(sounds);

View demo

Adjusting the volume

In the ‘play’ example, we created an AudioBufferSourceNode, and then connected it to a destination. To change the volume of an audio source, we need to create an AudioBufferSourceNode as before, but then we create a GainNode, and connect the AudioBufferSourceNode to that, before connecting the GainNode to the destination. Then we can use the GainNode to alter the volume.

sounds = {
  laser : {
    src : 'audio/laser',
    volume : 2
  },
  coin : {
    src : 'audio/coin',
    volume : 1.5
  },
  explosion : {
    src : 'audio/explosion',
    volume : 0.5
  }
};


/**
 * Example 4: Modify the playSoundObj function to accept volume property
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function playSoundObj(obj) {
  var source = context.createBufferSource();
  source.buffer = obj.buffer;

  // create a gain node
  obj.gainNode = context.createGain();

  // connect the source to the gain node
  source.connect(obj.gainNode);

  // set the gain (volume)
  obj.gainNode.gain.value = obj.volume;

  // connect gain node to destination
  obj.gainNode.connect(context.destination);

  // play sound
  source.start(0);
}
// loadSounds(sounds);

View demo

Muting a sound

To mute a sound, we simply need to set the value of the gain on the GainNode to zero.

var nyan = {
  src : 'audio/nyan',
  volume : 1
};
loadSoundObj(nyan);


/**
 * Example 5: Muting a sound
 * @param  {object} obj Object containing a loaded sound buffer.
 */

function muteSoundObj(obj) {
  obj.gainNode.gain.value = 0;
}
// muteSoundObj(nyan);

View demo

Looping sounds

Whenever you’re creating any game, you should always be mindful of optimising file sizes. There’s no point making your player download a 10Mb audio file, when the same effect is achievable with 0.5Mb of looped audio. This is especially the case if you’re creating games for HTML5 mobile game portals.

To create a looping sound, set the loop attribute of the AudioBufferSourceNode’s to true just before connecting it to the GainNode.

sounds = {
  laser : {
    src : 'audio/laser',
    volume : 1,
    loop: true
  },
  coin : {
    src : 'audio/coin',
    volume : 1,
    loop: true
  },
  explosion : {
    src : 'audio/explosion',
    volume : 1,
    loop: true
  }
};


/**
 * Example 6: Modify the playSoundObj function again to accept a loop property
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function playSoundObj(obj) {
  var source = context.createBufferSource();
  source.buffer = obj.buffer;

  // loop the audio?
  source.loop = obj.loop;

  // create a gain node
  obj.gainNode = context.createGain();

  // connect the source to the gain node
  source.connect(obj.gainNode);

  // set the gain (volume)
  obj.gainNode.gain.value = obj.volume;

  // connect gain node to destination
  obj.gainNode.connect(context.destination);

  // play sound
  source.start(0);
}
// loadSounds(sounds);

View demo

Crossfading sounds

Making use of multiple audio tracks is a great way to aurally demarcate the different areas of your game. In Brickout, for example, I crossfade between the title music and the game music when the game starts, and back again when it ends.

To crossfade between two tracks, you’ll need to schedule a transition between the gain volume ‘now’ and a time fixed in the future (e.g. ‘now’ plus 3 seconds). ‘Now’ in Web Audio terms is the AudioContext’s currentTime property – the time that has elapsed since the AudioContext was created.

var crossfade = {
  battle : {
    src : 'audio/the-last-encounter',
    volume : 1,
    loop : true
  },
  eclipse : {
    src : 'audio/red-eclipse',
    volume : 0,
    loop : true
  }
};


/**
 * Example 7: Crossfading between two sounds
 * @param  {Object} a Sound object to fade out.
 * @param  {Object} b Sound object to fade in.
 */

function crossFadeSounds(a, b) {
  var currentTime = context.currentTime,
      fadeTime = 3; // 3 seconds fade time

  // fade out
  a.gainNode.gain.linearRampToValueAtTime(1, currentTime);
  a.gainNode.gain.linearRampToValueAtTime(0, currentTime + fadeTime);

  // fade in
  b.gainNode.gain.linearRampToValueAtTime(0, currentTime);
  b.gainNode.gain.linearRampToValueAtTime(1, currentTime + fadeTime);
}
// crossFadeSounds(crossfade.battle, crossfade.eclipse);

View demo

LinearRampToValueAtTime has a catch that isn’t immediately apparent – if you try to change the gain after using it, nothing will happen. You need to cancel any scheduled effects you’ve applied before you can set the gain, even if your schedule has long since expired. You can do this with the cancelScheduledValues() method.

Final Tip

If you’re struggling to get a sense of what each of the vast array of audio nodes does, head over to the spec. Under each node’s subheading you’ll find the following:

numberOfInputs: n
numberOfOutputs: n

From this you can get a rough idea of what the node does. If the node has no inputs and one output, it will load or synthesise audio. If it has one input and n outputs, an audio source can be connected to it and modified or analysed in some respect. If the node has inputs but no output, it will be an end-point – the final destination that connects your audio to your user’s headphones or speakers.

Further Reading

https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

*The process of creating an audio sprite was painstaking – all of your game’s audio had to be composited into one file, and once loaded the ‘playhead’ had to be jogged back and forth between each of the ‘sprites’. This workaround still had it’s downsides – sounds were often clipped if a new sound was triggered before the previous sound had finished playing.

iOS 7 Safari: Breaking Changes for HTML5 Games

If you have any experience of making HTML5 games for mobile web browsers, you’ll recognise this bit of code:

// set height of the document to be greater than screen size
document.body.style.height = screen.height * 2 + 'px';

// scroll down the page a bit to hide url bar
window.scrollTo(0, 1);

// set the view frame to the height of window
if (window.innerWidth < window.innerHeight) {
    $frame.style.height = window.innerHeight + 'px';
}

This code is used to hide the URL bar in mobile browsers. The game view is fixed (using CSS) within the browser window – giving players the maximum space available to play your game.

iOS 6 on an iPhone 4S, window.scrollTo hack working

In iOS 7, Safari introduces ‘fullscreen browsing’ – as soon as a user scrolls down the page, both the url bar at the top and the navigation bar at the bottom are hidden from view. The bars are revealed again by browsing to the top of the page (or tapping the clock ‘scroll to the top’ shortcut).  This gives the user much more screen ‘real estate’ when browsing the web.

Unfortunately this feature, whilst being a step in the right direction for general purpose web consumption, is a real pain in the backside for HTML5 game developers. From my tests, it appears that any scroll behaviour must be user initiated. The window.scrollTo hack doesn’t work anymore. The page scrolls to whatever you set the Y value to, but then jumps back to Y = 0.

IMG_0075
iOS 7 on an iPhone 5, window.scrollTo hack no longer works 🙁

This change is going to (and has, because I’ve checked) cause problems for a lot of games. Luckily, the beta implementation of landscape mode never made it through to the final version.  That would have been a real nightmare for landscape-only games, because there was practically no viewport visible to play the game in.

IMG_0033-1
iOS 7 beta version of landscape mode
IMG_0077-1
iOS 7 final version of fullscreen landscape mode

This is bad news for HTML5 games and web apps alike.  I’d love to hear if you come up with a solution for this, because thus far, I’m stumped!

Introducing: PlayZap

PlayZap is a project that I’ve been working on, on and off, for about a year. It’s a games portal for mobile devices – users can play HTML5 games in their mobile browsers, without having to download an app from the app store.

Why?
I created PlayZap for two reasons: firstly, I wanted to build something cool with Node.js; and secondly, having built my first game with Canvas, I could see that it was something I wanted to do more of, and that having a portal to host the games on could be useful.

I also wanted to do something to help promote the mobile browser platform, my line of reasoning being that, more exposure would lead to more people playing HTML5 games on their mobile devices. This would lead to more portals being created, driving an increased demand for new games – a demand readily filled by developers that share the same love for the tech as I do. 🙂

Build
The portal took me a lot longer to build than I would have liked, largely because the first version was so ridiculously over-engineered. I’d only really tinkered with Node.js at this point, so I went into full-on ‘kid in a candy shop’ mode, and built whatever my whims dictated. User accounts, friends, ratings, favourites, challenges; a websocket (websockets! squeeee! :p) API for leaderboards and engagement tracking – you name it, I built it. Or at least half of it, because it’s always possible to make something better :).

Ultimately though, I realised that I’d gotten a bit carried away. I’d spent a huge amount of time building stuff that few people would use on day one – no-one was going to integrate my high scores API into their game on launch day. I’ve read Hacker News every day for the past two years – time and again, the best advice from developers is to release an MVP and iterate on it. It took a while for me to realise that the advice of celebrity engineers working on multi-million dollar projects was applicable to little ol’ me.

When the penny finally dropped last month, I stripped the site down to a few key components, and prepared to release it.

Launch
I launched the site last Friday. Big mistake. Another key piece of advice from Hacker News that I wilfully ignored – never launch on a Friday! I incorrectly reasoned that launching on the same day as OnGameStart – a (the) conference dedicated to HTML5 gaming – would garner a little bit more enthusiasm than if I had launched on an ordinary weekday. As it turned out, this was not the case.

Post-launch
Right now, I’m sitting on my couch in my pyjamas (I’ve been ill for about a week), trying to figure out where to go next. I’ve created a Facebook page, posted articles to FB promoting people’s games, and linked the portal’s Twitter account. I’ve posted to Reddit as frequently as the flood prevention mechanism would let me, and posted to a bunch of community forums. I’m in the middle of setting up Facebook advertising, and as soon as I’ve posted this, I’m off to LinkedIn to post to the groups I’m a member of.

I’d characterise the reaction to the launch as under-whelming – this of course is entirely my fault. Usually, I’d be disheartened by this – I’m quite a sensitive individual, and I have far too much of my ego invested in my work. But it’s been so extra-ordinarily freeing, just to launch the thing. It’s a weight off my mind – a giant check off my to-do list.

I plan to continue spreading the word as best I can, but if you have any tips or suggestions, I’d really appreciate it if you could post in the comments.

Thanks
I owe a huge debt of gratitude to the open source developers that contribute time, effort and expertise to making great software and releasing it to the community.  @tjholowaychuk, @aaronheckmann, @rauchg, @caolan, and all the guys at Maker – you are all awesome, thank you.

Thanks also to David, for putting up with me. x

Optimising HTML5 Canvas games

In this blog post I’m going to share a few tips and tricks I’ve picked up from the process of refactoring HTML5 Breakout. Some of these will be common sense performance tips; others might leave you scratching your head a bit.  I’ll start with a few general tips, and then move on to the canvas specific ones. (The best stuff is at the bottom).

Don’t prematurely optimise your code

This can cause all sorts of bugs, like breaking your collision detection.  You’ll end up having to backtrack, and it can become a bit of a nightmare, so it’s best not to do it!

Profile your code

Once you’ve finished your game, you need to profile your code.  It’s important to benchmark your code on all of the browsers you’re targeting.  Modern browsers compile and optimise your code, but the different JS engines do it in slightly different ways, so the function that runs lightning fast in one browser may perform sluggishly on another (for example, see ‘clear methods’ below).  David Mandelin at Mozilla did a great presentation on JS engine internals at VelocityConf, it’s well worth going through the slide deck if you’re interested in the nitty gritty. Go for the low hanging fruit here and investigate any code that is reported as running slow across all your profiles.

Minimise code in loops

This is a common sense best practice, but it becomes especially important when you’re doing something as CPU intensive as repeatedly drawing images to canvas.  When I was profiling Breakout, I discovered one function that was taking a particularly long time to execute.  It turned out that I had a call to fillRect in one of the loops in the drawBricks function. Simply moving this one line out of the loop shaved 50% of the execution time of that function.  Little fixes that result in huge performance increases are always the most satisfying 🙂

Minimise draw calls

As mentioned above, constantly drawing and redrawing images is hard work, so don’t draw if you don’t have to!  It can be tempting to draw a frame and then clear the whole canvas repeatedly in your game loop, as it simplifies development. However, it doesn’t do much good for your game’s performance.  If you can figure out which parts of your canvas have changed since the last frame, and only redraw those parts (by using ‘dirty rectangles‘), your game should see a huge performance boost.

Use more than one canvas

If you draw too many pixels to the same canvas at the same time, your frame rate will fall through the floor.  In these circumstances, it’s better to use multiple canvasses layered on top of one another. Take Breakout for example. Trying to draw the bricks, the ball, the paddle, any power-ups or weapons, and then each star in the background – this simply won’t work, it takes too long to execute each of these instructions in turn.  By splitting the starfield and the rest of the game onto separate canvasses, I was able to ensure a decent framerate.  This is a useful technique for animation-heavy games.

Pixel snapping

If you’re a front-end developer used to working with the DOM, the concept of sub-pixel rendering will be a little odd – we’re just not used to seeing anything with units of measurement less than a pixel.  Canvas can render your images at positions less than a pixel, anti-aliasing them in the process.  Anti-aliasing canvas is currently super slow on some platforms, so it’s best to avoid it if you can by rounding off the positions of your game entities just before each frame is drawn.  I’ve been using | 0 (bitwise OR) – it’s faster than Math.round() as it doesn’t have the function call overhead, but it seems like the best method changes with each incremental browser version.  Check with JSPerf and pick the best method for your use case. And read Seb Lee Delisle’s HTML5 canvas sprite optimisation post, he explains this much better than I do 🙂

Clear methods

There are three ways to clear your canvas: fillRect using your background colour, clearRect, and resetting the canvas’ width (or height) property.  Resetting the width of the canvas is supposedly the fastest method, but if you look at the JSPerf tests – particularly the differences between Chrome 14 and Firefox 4 – the execution speeds documented are wildly at odds with each other.  From this data, it appears that width = width performs at the same speed or faster (much faster in some instances) than the other two methods on mobile, but when it comes to desktop browsers, all bets are off.

Another issue worth thinking about – if you’re doing a lot of transforms, width = width resets the transform stack, whereas clearRect and fillRect don’t.  So if you’re doing a lots of transforms and don’t want to reset state, clearRect is probably the best way to go.  Simon Sarris did a good write up of this called How you clear your HTML5 Canvas matters.

From JSPerf:

UserAgent clearing non transformed canvas clearing transformed canvas setting width # Tests
Chrome 14.0.786 14,779 14,495 417,562 1
Chrome 14.0.789 15,842 15,258 407,201 3
Firefox 4.0.1 188,673 143,651 1,741 27

Use requestAnimationFrame instead of setInterval/setTimeout

SetInterval and setTimeout were never intended to be used as animation timers, they’re just generic methods for calling functions after a time delay.  If you set an interval for 20ms in the future, but your queue of functions takes longer than that to execute, your timer won’t fire until after these functions have completed.  That could be a while, which isn’t ideal where animation is concerned.  RequestAnimationFrame is a method invented by Robert O’Callahan at Mozilla – it specifically tells the browser that an animation is taking place, so it can optimise repaints accordingly.  It also throttles the animation for inactive tabs, so it won’t sap your mobile device’s battery if you leave it open in the background.

Nicholas Zakas wrote a hugely detailed and informative article about requestAnimationFrame on his blog which is well worth reading.  If you want some hard and fast implementation instructions, then Paul Irish has written a requestAnimationFrame shim – this is what I’m currently using in my games.

Canvas has a back-reference

And finally, a useful tip I picked up at Remy Sharp’s HTML5 Workshop – your 2d context has a back reference to it’s associated DOM element:

var ctx = doc.getElementById('canvas').getContext('2d');
console.log(ctx.canvas);    // HTMLCanvasElement

This can come in pretty handy!

I hope you’ve enjoyed my whistle-stop tour of canvas performance optimisation techniques. If you spot any errors or glaring omissions, please leave a comment below 🙂

Cheers!