Friday, April 26, 2013

Objects in HTML5 canvas (part 3: interacting with images)

This article is the third of a series of simple recipes that explains how to interact with objects in HTML5 canvas.
  • part 1 (Drawing shapes)
  • part 2 (Interacing with objects)
In this part we will solve the last problem: how to interact with objects with irregular shapes.

The simple answer is that you don't have to. You can wrap any image inside a rectangular box and detect where the image is transparent.

Introducing offscreen canvas

An offscreen canvas is a canvas outside the DOM. It's very useful for 2 reasons:
  • you can do complex drawing once and reuse the result with just an instruction
  • you can use it to check where an image is transparent

Let's draw

First of all I start with an images array (I used inline images, but you can use images urls):

var objs = [

Then I draw each image inside an offscreen canvas and, then the offscreen canvas in the main canvas:

var i, obj;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0;i < objs.length;i++){
    obj = objs[i];
    (function (obj){
        var img = new Image();   // Creating a new img element
        img.onload = function(){
            // It's very important to wait until the image loads!!!
            // otherwise I have no clue of the real size of the image 

            ctx.translate(obj.x, obj.y);
            obj.width = img.width;
            obj.height = img.height;
            //I attach the offscreen canvas to the object
            obj.canvas = document.createElement("canvas");
            obj.canvas.width = img.width;
            obj.canvas.height = img.height;
            obj.ctx = obj.canvas.getContext('2d');
            // I draw the image on a off-screen canvas
            obj.ctx.drawImage(img, 0 , 0);
            // I draw the off-screen canvas on the main canvas
            ctx.drawImage(obj.canvas, -img.width/2 , -img.height/2);
        img.src = obj.src; // Set source path

Pay attention! I have applied transformations on the main canvas and not on each  off-screen canvases.

Detecting mouse clicks

Let's finish detecting on which shape a user is pointing. As usual I check each object in reverse order and transform pointer coordinates. Then I must retrieve the image from the offscreen canvas and test the color on the resulting coordinates. If the alpha value is opaque the pointer is on the image.

    // I get a single pixel
    imageData = obj.ctx.getImageData(p.x+(obj.width/2), p.y+(obj.height/2), 1, 1);
    // imageData contains r,g,b,a
    if([3] > 50){ // 50 is the threeshold
        log.innerHTML = 'clicked on ' +;

I have taken a pixel from the canvas using getImageData. This function returns  an array that include 4 values for each pixel (just one in my case).
The values are Red, Green, Blue and Alpha (transparency) and they have values between 0 and 255.
I set a threeshold of 50 on alpha.
This is the whole code:

canvas.addEventListener('click', function (evt){
    var rect = canvas.getBoundingClientRect(),
        posx = evt.clientX - rect.left,
        posy = evt.clientY -,
        i = objs.length,
        for (; i > 0; i--){
            obj = objs[i-1];
            // translate coordinates
            p = new Point(posx, posy);
            p.translate(-obj.x, -obj.y);

            imageData = obj.ctx.getImageData(p.x+(obj.width/2), p.y+(obj.height/2), 1, 1);

            if([3] > 50){
                log.innerHTML = 'clicked on ' +;
        log.innerHTML = '';

}, false);

And this is the results:
  Your browser doesn't support canvas!

I hope this is helpful!