Optimizing my JavaScript Canvas Game Pt 1

Taylor Nodell
4 min readMar 3, 2017

My portfolio is a game made in Vanilla JavaScript and the HTML5 Canvas element. I was relatively happy with it when I had all the parts moving. It resizes based on screen size and even has touch controls for mobile. But I couldn’t deny the glaring errors that a quick look at the Chrome Dev Tool’s Timeline revealed.

A jank filled timeline

All those red marks are like the proverbial teachers pen. They represent long frames and they’re all over the place. Browsers try to match the refresh cycle of device’s screen, for most cases this is 60 frames per second. Long frames take more time to render than that 60 FPS rate, and can cause the animations to skip. In my case the player jumps through space and time unexpectedly, and overall just feel bad to the user. So how do you fix this? A closer look at one frame in the Timeline.

This is a bad frame

Here you can see a single frame and where the browser is spending it’s time. The entire animation process took 40.8ms. Which is pretty bad. Ideally, you want to get all your work done in 16.6ms, which corresponds to 60 frames per second, the golden standard in gaming. If you want an awesome explainer of the value of high frame rates, check out this Super Bunnyhop video.

checkPlatforms is sparking off a bunch of functions that don’t need to happen

Falling through the flame chart you can see the functions being called in each frame. I’m ignoring the first couple of calls since I know I need to do a requestAnimationFrame, there’s nothing I can change about that. But then I see update, a function I wrote, and the child function checkplatforms. This is taking up 21ms alone, so there’s obviously a problem.

checkPlatforms contains the code for… well checking if the player is on a platform. Descriptive naming is important. That overlap function with 8 arguments is just an Axis Aligned Bounding Box check.

function checkPlatforms(entity){
/*Fade in for overlap or click*/
for (n = 0; n < platforms.length; n++) {
if (overlap(entity.x, entity.y, TILE, TILE, platforms[n].start.x, platforms[n].start.y, platforms[n].width, platforms[n].height)) {

platformDOs[n].fadeIn(“slow”);

}
if (!overlap(entity.x, entity.y, TILE, TILE, platforms[n].start.x, platforms[n].start.y, platforms[n].width, platforms[n].height) && platforms[n].clicked == false) {

platformDOs[n].fadeOut(“slow”);

}
}
}

Basically I’m saying; for every platform, if you’re standing on the platform, fade in the corresponding platform div. But! I’m also saying, if you’re not standing on the platform, fade that div out. Which works, but runs an animation every single frame for every platform. A huge overhead that doesn’t need to happen.

Instead of leaning on that jQuery crutch, I can rewrite my own fadeIn and fadeOut functions. Lovingly named fadeInT and fadeOutT (T forTaylor), they check the class list of the element, and if it has hidden (the initial class when the game starts) or fadeout (applied after the player jumps out of the platform), then remove those and add the fadeIn.

function fadeInT(el) {
if (el.classList.contains('hidden')||el.classList.contains('fadeOut')){
el.classList.remove('hidden');
el.classList.remove('fadeOut');
el.classList.add('fadeIn');
}
}

Then we have CSS doing the animation, with the appropriate polyfills.

.fadeIn{
-webkit-animation: fadein 2s forwards; /* Safari, Chrome and Opera > 12.1 */
-moz-animation: fadein 2s forwards; /* Firefox < 16 */
-ms-animation: fadein 2s forwards; /* Internet Explorer */
-o-animation: fadein 2s forwards; /* Opera < 12.1 */
animation: fadein 2s forwards;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}

This has the added effect of completely removing jQuery in my code, cutting out an entire library I was loading just for that one piece of code. If you find yourself in the same situation checkout youmightnotneedjquery. It’s really helpful if your site is just barely using jQuery, there’s a simple JavaScript equivalent for just about everything.

Now that I’ve stopped doing unnecessary animating, lets look back at our timeline and see how long checkPlatforms takes.

Tiny baby update

Oh wait, that’s right. ITS MOSTLY GONE. Because I’m just doing two evaluations now, if I’m on a platform and if that element has a class. In that particular case then do the CSS animation. WHICH IS THE WAY IT SHOULD BE.

However, should I be fulfilling those cases, then my little baby update is only 0.12ms. A nearly 183% improvement. Congrats me, if I had cut costs that much while improving customer relations at a store, I would be manager of the year.

While that was a great improvement, there’s still more to be done to reach a silky smooth consistent 60 fps. Part two deals with caching a canvas image, so I’m not redrawing something that doesn’t change each frame.

For the code and full attributions, here’s the github.

--

--

Taylor Nodell

Developer. Musician. Naturalist. Traveler. In any order. @tayloredtotaylor