Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 Canvas Performance and Optimization Tips, Tricks and Coding Best Practices [closed]

Redraw Regions

The best canvas optimization technique 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.

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.

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. But 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) and context.restore() (reverts to the previous state). This enables 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. This can be especially useful.

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 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.

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/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.

Optimization techniques


Here are my tips

1) Use clearRect to clear the canvas instead of canvas.width=canvas.width, because later resets the canvas states

2) If you are using mouse events on the canvas use following function, its is reliable and works in most of the cases.

/**  returns the xy point where the mouse event was occured. 
 @param ev The event object.
*/
function getXY(ev){
   return getMousePosition(ev, ev.srcElement || ev.originalTarget);
}

 /**  returns the top-left point of the element
       @param elem The element
   */
function getElementPos(elem){
   var obj = elem;
   var top = 0;
   var left = 0;
    while (obj && obj.tagName != "BODY") {
      top += obj.offsetTop-obj.scrollTop;
      left += obj.offsetLeft -obj.scrollLeft ;
      obj = obj.offsetParent;
     }
  return {
    top: top,
    left: left
    };
};

/**  returns the xy point where the mouse event was occured inside an element. 
@param ev The event object.
 @param elem The element
*/
function getMousePosition(evt, elem){
var pageX, pageY;
if(typeof(window.pageYOffset)=='number') {
    pageX=window.pageXOffset;
    pageY=window.pageYOffset;
}else{
    pageX=document.documentElement.scrollLeft;
    pageY=document.documentElement.scrollTop;
}
var mouseX = evt.clientX - getElementPos(elem).left + pageX;
var mouseY = evt.clientY - getElementPos(elem).top + pageY;
return {
    x: mouseX,
    y: mouseY
};
};

3) Use ExplorerCanvas if you want to support IE7

4) Instead of clearing the whole canvas clear only the part which is needed to be cleaned. Its good for performance.


Here's some more tips and suggestions I put into a list last night worth sharing.

  • Don't include jQuery unless you need to do more than just selecting the <canvas>.

    I've managed to get by without it for almost everything I've made in canvas

  • Create abstracted functions and decouple your code. Separate functionality from appearance or initial draw state.

    Make common functions reusable as much as possible. Ideally, you should use a module pattern, which you can create a utils object that contains common functions.

  • Use single and double letter variable names 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.

    I suggest you type out or abbr. the word (dirX, accelX, velX) or be descriptive, otherwise things could get pretty confusing for you later on, trust me.

  • Create constructor functions which can be invoked as needed for making game elements. You can add custom methods and properties within the constructor, and create any number of you may need and they all will have their own properties and methods.

    Example of a Ball constructor function I made:

    // Ball constructor
    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();
        };
    };
    

Creating the Ball

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, making updates to the changing parts or elements. Create the game elements do any other UI work outside the animation loop.

    The animation loop is often a recursive function, meaning 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 your not already, and then within the constructor make a 'timer' method that has requestAnimFrame/setTimeout using it just how you would normally within any animation loop, but effects this element specifically only.

    You could make each game element have it's own timer, draw, and animate methods in the constructor.

    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.

Or another option:

  • Create a Timer() constructor function which you can use and give each animating element individually, thereby minimizing work load within animation loops

After having worked on a recently launched Facebook app that uses Canvas and users Facebook profile information (the amount of data it must accommodate is massive for some) to match you and friends of yours also using the app, to Olympic athletes like 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 re-factor the code which I knew already so well, and believed it to be the most optimal way to do things.

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 IE 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.