Skip to content

Instantly share code, notes, and snippets.

@jaredwilli
Last active February 9, 2024 10:29
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jaredwilli/5469626 to your computer and use it in GitHub Desktop.
Save jaredwilli/5469626 to your computer and use it in GitHub Desktop.
HTML5 Canvas Performance and Optimization Tips, Tricks and Coding Best Practices

HTML5 canvas Performance and Optimization Tips, Tricks and Coding Best Practices With canvas being still very new to internet, and no signs of it ever getting old that I can see in the future, there are not too many documented best practices or other really important tips that are a must know for developing with it in any one particular place. Things like this are scattered around and many times on lesser known sites.

There's so many things that people need to know about, and still so much to learn about, so I wanted to share some things to help people who are learning canvas and maybe some who already know it quite well and am hoping to get some feedback from others about what they feel are some best practices or other tips and tricks for working with canvas in HTML5.

I want to start off with one I personally found to be quite a useful yet surprisingly uncommon thing for developers to do. Indent your code Just as you would any other time, in any other language whatever the case may be. It has been a best practice for everything else, and I have come to find that in a complex canvas app, things can get a little confusing when dealing with several different contexts and saved/restore states. Not to mention the code is just more readable and overall cleaner looking too.

For example:

ctx.fillStyle = 'red';
ctx.fill();
ctx.save();
    if (thing < 3) {
        // indenting
        ctx.beginPath();
            ctx.arc(2, 6, 11, 0, Math.PI*2, true);
        ctx.closePath();
        ctx.beginPath();
            ctx.moveTo(20, 40);
            ctx.lineTo(10, 200);
            ctx.moveTo(20, 40);
            ctx.lineTo(100, 40);
        ctx.closePath();
        ctx.save();
            ctx.fillStyle = 'blue'
            ctx.fill();
        ctx.restore();
    } else { 
        // no indenting
        ctx.beginPath();
        ctx.arc(2, 6, 11, 0, Math.PI*2, true);
        ctx.closePath();
        ctx.beginPath();
        ctx.moveTo(20, 40);
        ctx.lineTo(10, 200);
        ctx.moveTo(20, 40);
        ctx.lineTo(100, 40);
        ctx.closePath();
        ctx.save();
        ctx.fillStyle = 'blue'
        ctx.fill();
        ctx.restore();
    }
ctx.restore();
ctx.drawRect();
ctx.fill();

The if statement not easier and cleaner to read and know what is what immediately going on than the else statement in this, is it not? I think this should be a method that developers should continue to practice just as they would when writing plain 'ol javascript or any other language even.

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 which tells the browser that an animation is taking place, so it can optimize repaints accordingly. It also throttles the animation for inactive tabs, so it won't kill 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 which I've used in every one of the canvas apps I have made just about.

Update: Even better than using requestAnimationFrame in place of setTimeout and setInterval, Joe Lambert has written a new and improved shim called requestInterval and requestTimeout, which he explains what issues exist when using requestAnimFrame. You can view the gist of the script here.

Update #2: Now that all the browsers have caught up on the spec for this, there has been an update to the requestAnimFrame polyfill, one which will probably remain the one to use to cover all vendors.

Use more than one canvas This is a technique for animation-heavy games which @nicolahibbert wrote about in a post of hers on optimizing canvas games. She explains how it may be better to use multiple canvases layered on top of one another rather than do everything in a single canvas.

"Drawing too many pixels to the same canvas at the same time will cause your frame rate to fall through the floor. 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 canvases, you are able to ensure a decent framerate." Nicola says.

Render Elements Off-screen I have had to do this for a few canvas apps I've made including Samsung's Olympic Genome Project facebook app. It's an extremely useful thing to know and to make use of whether it's needed or not. It decreases load time immensely, plus it can be a really useful technique to load images off screen since they can sometimes take a while.

var tmpCanvas = document.createElement('canvas'),
    tmpCtx = tmpCanvas.getContext('2d'),
    img = document.createElement('img');

img.onload = function() {
    tmpCtx.drawImage(thumbImg, 0, 0, 200, 200);
};
img.src = '/some/image/source.png';

Notice that the src of the image is set after it is loaded. This is a key thing to remember to do too. Once the images are done loading and drawn into these temp canvases, you can then draw them to your main canvas by using the same ctx.drawImage(), but instead of putting the image as the first argument, you use tmpCtx.canvas to reference the temporary canvas.

Other Resources Canvas test cases Some more canvas and JS tests HTML5Rocks Performance Improving requestAnimFrame to Optimize Dragging Events

Canvas Has a Back Reference The 2d Context has a back reference to it's associated DOM element that you can use for quick referencing of the context which is HTMLCanvasElemen.

For example:

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

I would like to get more information on this and other shortcut references that may exist in canvas as well, but this is one that is pretty straightforward I think.

Redraw Regions One of the best canvas optimization techniques for animations is to limit the amount of pixels that get cleared/painted on each frame. The easiest solution to implement is resetting the entire canvas element and drawing everything over again but that is an expensive operation for your browser to process.

The idea is to reuse as many pixels as possible between frames. What that means is the fewer pixels that need to be processed each frame, the faster your program will run. For example, when erasing pixels with the clearRect(x, y, w, h)method, it is very beneficial to clear and redraw only the pixels that have changed and not the full canvas.

Drawing with Procedural Sprites Generating graphics procedurally is often the way to go, but sometimes that's not the most efficient one. If you're drawing simple shapes with solid fills, then drawing them procedurally is the best way do so. However, if you're drawing more detailed entities with strokes, gradient fills and other performance sensitive make-up you'd be better off using image sprites.

It is possible to get away with a mix of both. Draw graphical entities procedurally on the canvas once as your application starts up. After that you can reuse the same sprites by painting copies of them instead of generating the same drop-shadow, gradient and strokes repeatedly.

State Stack & Transformation The canvas can be manipulated via transformations such as rotation and scaling, resulting in a change to the canvas coordinate system. This is where it's important to know about the state stack for which two methods are available:

context.save() - pushes the current state to the stack context.restore() - reverts to the previous state

These enable you to apply transformation to a drawing and then restore back to the previous state to make sure the next shape is not affected by any earlier transformation. The states also include properties such as the fill and stroke colors.

Compositing A very powerful tool at hand when working with canvas is compositing modes which, amongst other things, allow for masking and layering. There's a wide array of available composite modes and they are all set through the canvas context's globalCompositeOperation property. The composite modes are also part of the state stack properties, so you can apply a composite operation, stack the state and apply a different one, and restore back to the state before where you made the first one. So it can be especially useful for this reason.

Anti-Aliasing To allow for sub-pixel drawings, all browser implementations of canvas employ anti-aliasing (although this does not seem to be a requirement in the HTML5 spec). Anti-aliasing can be important to keep in mind if you want to draw crisp lines and notice the result looks blurred. This occurs because the browser will interpolate the image as though it was actually between those pixels. It results in a much smoother animation (you can genuinely move at half a pixel per update) but it'll make your images appear fuzzy.

To work around this you will need to either round to whole integer values or offset by half a pixel depending on if you're drawing fills or strokes.

Using Whole Numbers for drawImage() x and y Positions If you call drawImage()on the canvas element, it's much faster if you round the x and y position to a whole number.

Here's a test case on jsperf showing how much faster using whole numbers is compared to using decimals. So it is a good idea to round your x and y position to whole numbers before rendering.

Faster than Math.round() Another jsperf test shows that Math.round()is not necessarily the fastest method for rounding numbers. Using a bitwise hack actually turns out to be faster than the built in method.

Here’s a good article on canvas Sprite Optimization.

Clearing the canvas To clear the entire canvas of any existing pixels context.clearRect(x, y, w, h) is typically used – but there is another option available. Whenever the width and height of the canvas are set (even if they are set to the same value repeatedly) the canvas is reset. This is good to know when working with a dynamically sized canvas as you will notice drawings disappearing.

Computation Distribution The Chrome Developer Tools profiler is very useful for finding out what your performance bottlenecks are. Depending on your application you may need to refactor some parts of your program to improve the performance and how browsers handle specific parts of your code.

Read more on canvas optimization techniques.

Here's some more tips and suggestions I put into a list worth sharing:

Don't include jQuery unless you need to do more than just selecting the . I've managed to get by without it for almost everything I've made in canvas Create abstracted functions and decouple your code. Separating functionality from appearance or initial draw state as much as possible can be very helpful in the long run and is just good practice in general. Make common functions reusable as much as possible. Ideally, you should use a module pattern or some sort of abstracted API that breaks up code that you can reuse. I like to make a separate object that contains common functions and utilities. Use single and double letter variable names only when it makes sense (x, y, z). The coordinate system in canvas adds more single letters that are commonly declared as variables. Which can lead to creating multiple single/double variables (dX, dY, aX, aY, vX, vY) as part of an element. It’s better to be verbose and type out or at least abbreviate the variable names (dirX, accelX, velX) otherwise things could get pretty confusing for you later on. I’ve seen many people not doing this and it should be reiterated as a best practice. Make constructor functions for generating anything that you will need more than one of. These can be useful for anything, whether you want to make multiples of the same shape, or at a lower level make vectors which add actions or other things to the prototype.

An example of a constructor I made for creating circles:

var Ball = function(x, y) {
   this.x = x;
    this.y = y;	
    this.radius = 10;
    this.color = '#fff';
    // Direction and min, max x,y
    this.dX = 15;
    this.dY = -15;
    this.minX = this.minY = 20 + this.radius;
    this.maxX = this.radius - (canvasWidth - 20);
    this.maxY = this.radius + canvasHeight;
    this.draw = function(ctx) {
        ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, twoPI, true);
            ctx.closePath();
            ctx.save();
    	    ctx.fillStyle = this.color;
    	    ctx.fill();
    	ctx.restore();
    };
};

Then to create a ball you would do:

ball = new Ball(centerX, canvasHeight - paddle.height - 30);
ball.draw(ctx);

A good base to work with is to create 3 functions init() - do all the initial work, and setup the base vars and event handlers etc. draw() - called once to begin the game and draws the first frame of the game, including the creation of elements that may be changing or need constructing. update() - called at the end of draw() and within itself via requestAnimFrame. Updates properties of changing elements, only do what you need to do here. Do the least amount of work within the loop updating and drawing only the changing pixels. Create the game elements and do any other UI work outside the animation loop. The animation loop is often a recursive function, which means it calls itself rapidly and repeatedly during the animation to draw each frame. If there are many elements being animated at once, you might want to first create the elements using a constructor function if you’re not already, and then within the constructor make a timer method that has requestAnimFrame/setTimeout sing it just how you would normally within any animation loop, but effects this element specifically only. Consider adding timer(), draw() and animate() methods on each of your constructors for things that need to animate and for varying amounts of time. Doing this gives you full separation of control for each element and one big animation loop will not be necessary at all since the loop is broken up into each element and you start/stop at will. Alternatively, create a Timer() constructor which you can use and give each animating element individually, thereby minimizing workload within animation loops.

After having worked on a large Facebook app which a canvas data-visualization as the primary focus and incorporated each users Facebook profile information (a massive amount of data for some people) to match you (and friends of yours also using the app) to Olympic athletes (a 6 degrees of separation type of thing) there's quite a lot I have learned in my extensive efforts to do everything I could possibly try for increasing performance within the app.

I literally spent months, and days at a time just working to refactor the code which I knew already so well, and believed it to be the most optimal way to do things. As it turned out in the end a valuable lesson I learned brings me to this last thing.

Use DOM Elements Whenever Possible The fact is, browsers are still just not ready to handle more intensive running applications in canvas, especially if you're required to develop the app with support for Internet Explorer 8. There are sometimes cases where the DOM is faster than the current implementation of the canvas API at the time of writing this. At least I've found it to be while working on a massively complex single page animating html5 and canvas application for Samsung.

We were able to do quite well at improving the performance of things while still using canvas to do some complex work to crop images into circles, which would've probably been ok to stick with how we were doing it.

Days before the launch, we decided to try a different technique, and rather than create temporary canvases off-screen which were placed on the visible canvas once cropped into circles etc., we just appended Image DOM elements on the canvas, using the x and y coordinates that we had been using for placing the temp canvases before.

For cropping the images into circles, well that was simple, we just used the CSS3 border-radius property to do it which was far less work than the complex series of state changes and while ingenious and creative yet over-use of the clip() method.

Once they are placed in the DOM, the animation of images the occurs, and the DOM nodes for each image are animated as separate entities of the canvas. Ones that we can have full control over the styling off easily through CSS.

This technique is similar to another method for doing this type of work that is quite good to know as well, which involves layering canvases on top of each other, rather than draw them to one context.

@jamesseanwright
Copy link

This is awesome, thank you 😄

@Lupusa87
Copy link

Please format text to be easy readable.

@vishnu-dev
Copy link

Please format text to be easy readable.

+1

@jaredwilli
Copy link
Author

@Lupusa87 and @vishnu-dev there you go :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment