Using canvas and multi touch

I'm excited about the possibilities that touch screens and multi-touch screens will give us. I'm also interested in HTML 5 and making something that will work accross a variety of devices whether they have a touch screen or just a mouse. To play about with both I'm currently making a colouring book app that will work on an iPad, a desktop computer's browser and hopefully any other touch enabled phone browser.

<Canvas> is a new HTML tag that is part of HTML 5. It acts like a drawing surface that you draw on using JavaScript commands.

In JavaScript you can catch mouse and touch events. At first I thought that an iPad would issue mouse events as well as touch events, but it doesn't. Touch and mouse events are slightly different, but when handled correctly you can make a good interface that works with both.

This is just a simple example that responds to both touch and mouse events to draw a line on the HTML 5 canvas screen. It will work on an iPad, iPhone (and hopefully Android and other touch phones) using touch and touches. It will also work in any modern browser that supports the <canvas> tag.

To try it out click on the box above and while the mouse is still pressed drag it about. Or, if you have a touch device try touching it with one or more fingers.

The code

The HTML for the canvas tag is very simple:

<canvas id="cbook" width="400" height="300" style="position: relative;">
<strong>[Your browser can not show this example.]</strong>
</canvas>

It's a lot like an image tag, except it needs the closing tag. The content enclosed between the two canvas tags will only be seen by browsers that don't support canvas, like Internet Explorer 8. The position relative bit is needed to make capturing the mouse position work across different browsers.

The JavaScript for this was a bit more complicated. Drawing to a canvas element is very easy, capturing mouse and touch events was a bit more difficult, but the most difficult bit was working out where the mouse was in a way that worked on all the different browsers.

You can download my full JavaScript Source here, but I'll go over some of the key bits.

Drawing on the canvas area

I found Firefox's Canvas Tutorial a very helpful guide to get started with using canvas.

Here's the code I needed to know to draw lines on the canvas:

// Get canvas HTML element
var canvas = document.getElementById("cbook");

// Check that canvas is supported by the browser
if (canvas.getContext) {
    // Get a context to draw with
    ctx = canvas.getContext('2d');

    // Set line style
    ctx.lineWidth = 2;
    ctx.strokeStyle = "rgb(0, 0, 0)";

    // Draw a line
    ctx.beginPath();
    ctx.moveTo(sX, sY);
    ctx.lineTo(eX, eY);

    // Add stroke to line
    ctx.stroke();

    ctx.closePath();
}

It's quite easy really. The main things that caught me out were that a line will not appear on screen until you add a stroke to it. And, if you do not close and start a new path the stroke will get applied again to all lines since beginPath was called.

Capturing mouse and multitouch events

I found this article about touching and gesturing on the iPhone very helpful in getting this to work. I couldn't have figured out multi touch events without it. I wanted my example to work with a mouse as well as with touch and this took a little bit more work.

Here is how you can capture a mousemove and touchmove event:

// Set up handlers (this needs to be done in an onload event)
canvas.ontouchmove = moveEventFunction;
canvas.onmousemove = moveEventFunction;

function moveEventFunction(e) {
    if (e.touches) {
        // Touch Enabled (loop through all touches)
        for (var i = 1; i <= e.touches.length; i++) {
            var p = getCoords(e.touches[i - 1]); // Get info for finger i
            // ... Do something with point touch p
        }
    }
    else {
        // Not touch enabled (get cursor position from single event)
        var p = getCoords(e);
        // ... Do something with cursor point p
    }

    return false; // Stop event bubbling up and doing other stuff (like pinch zoom or scroll)
}

Working out the cursor position relative to the element it clicked on

Getting this to work in all the browsers I tested made me very angry. It really shouldn't have been this hard. There are a few JavaScript libraries that make this easier, but it didn't seem worth it just to get this one simple thing to work.

Here is the code I finally came up with:

// Get the coordinates for a mouse or touch event
function getCoords(e) {
    if (e.offsetX) {
        // Works in Chrome / Safari (except on iPad/iPhone)
        return { x: e.offsetX, y: e.offsetY };
    }
    else if (e.layerX) {
        // Works in Firefox
        return { x: e.layerX, y: e.layerY };
    }
    else {
        // Works in Safari on iPad/iPhone
        return { x: e.pageX - cb_canvas.offsetLeft, y: e.pageY - cb_canvas.offsetTop };
    }
}

It still needs some more testing. I've tested it in the latest versions of Firefox, Safari (desktop/iPhone/iPad), Chrome. I've not tested it in IE 8 because this example already needs canvas which IE8 doesn't support. I've got high hopes for IE 9 though.

These little code snippets should give you some idea of how the whole thing works but you really need to refer to the full JavaScript Source to see how it works fully.

JP said

Excellent article, and relevant to my interests. Thanks for spending the time to figure out the difficult bits so other people don't have to :D

Paul said

Works on my dell streak.

Hal gumbert said

Has any one gotten this to work in IE with either ExplorerCanvas or Google Frame?

If so, could you share your code?

Richard Garside said

@Hal I've not tried, but I'd be interested if anyone else has.

Lee said

Is it possible to remove the for loop so the touch works with only one finger?

Richard Garside said

@Lee yes it is. Just remove the loop and access the first touch event in the array.

eaglejohn said

Is it possible to erase the drawing without reloading the page?

Richard Garside said

@eaglejohn you can clear the canvas using:

ctx.clearRect(0,0,canvasWidth,canvasHeight)

Jussi Kauhanen said

Hello! Big thanks for the great example! I just got into PHP-coding and this example proved to be a big help on the iPhone / iPad -front.

Tested and working fine on: FF 10, IE 9, Chrome 16, and iPhone 3GS (newest software-patch).

I have cobbled together a small drawing program, where i use (on the mobile device side) your event handling code with an ugly hack to change the colors :)

Hope you don't mind and all the best from Finland!

Jello said

I noticed that when you make the stroke size larger, the stroke is made of rectangles so if the line becomes very choppy. Is there some way to change the stroke to circles (or something else) so the line is smoother at larger widths?

Jose Antonio said

Can Anyone help me? I've been trying this code and nothing. the html
<!DOCTYPE html> <html> <head> </head> <body> <h2>Canvas</h2> <p><canvas id="cbook" width="400" height="300" style="position: relative; border: 2px solid #ccc;"> <strong>[Your browser can not show this example.]</strong> </canvas> </p> <script src="colouring.js" type="text/javascript"/> </body> </html>

And I've been using the same colouring.js

Richard Garside said

@Jose the only thing I can see is that you've used a self closing script tag and I think I remember that causing me problems in the past. Try including the </script> tag and see if that helps.

David said

"@jello to prevent the line from getting "choppy", add

context.lineCap="round";

This worked for me.

Gauri said

I'm new in HTML 5 canvas. I build an application in which I'm drawing a line and then zooming that area. But the moment I zoom an area my coordinates go wrong and it draws line in another co-ordinate.

Can you please help me, to how to draw a line in zoomed canvas using JavaScript in HTML5 canvas.

Richard Garside said

@Gauri It depends how you've zoomed. You'll probably need to scale your coordinates by the amount you've zoomed and also translate them if the zoomed in section isn't central. It might take a bit of trial and error, but I'm sure you'll figure it out.