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.

[Your browser can not show this example.]

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 = cb_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
	cb_ctx.stroke();

	cb_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++) {
			// Get info for finger i
			var p = getCoords(e.touches[i - 1]);

			DoSomethingWithPoint(p);
		}
	}
	else {
		// Not touch enabled
		// (get cursor position from single event)
		var p = getCoords(e);

		DoSomethingWithPoint(p);
	}

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

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.

No Comments

  • 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

  • Andrewiski said

    add the explorercanvas.js to add canvas to internet explorer. You will need to chekc if ie to prevent script error on even e.touches but other then that it should work

  • 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?

  • Paul Telford said

    Hi and thanks for this superb article.

    I'd like to capture the canvas contents and stuff into a database - can anyone point me in the right direction please?

  • Paul Bevis said

    Hey, great article, got me out of a hole!

    If you include jquery though you could maybe reduce getCoords method down to the following...

    return { x: e.pageX - $("#cbook").offset().left, y: e.pageY - $("#cbook").offset().top };

    That seemed to work for iPad/Safari/Firefox/Chrome for me...

  • Sloppy said

    After testing,it can not work on the Chrome(12.0.742.112)/Safari(5.0.5)/Firefox(5.0, our test browser is all for desktop. Can you tell me if we need to do some configuration to run the demo successfully?

  • Richard said

    I've just tested in FF 5 on Windows and it still works fine. I've not done exhaustive testing on new browsers though since I completed the code sample when I wrote this post.

  • Abhishek said

    There seems to be a problem with your coordinate offset detection, its offsetting the offset... Lol...

    So the only point which is accurate is the 0,0 coordinate of the element, extreme top left... Everything else is offset by itself, so the coordinate 20,30 is input by the user, but the canvas draws at coordinate 40,60...

    Please check your code...

  • vivid said

    Hi, thanks for the great script. It's the best cross platform touch demonstration I have seen. One question tho, I am having trouble detecting a single 'touchup' event. I can see it fire from 2 fingers, when one is lifted, but not one finger when it's lifted. Is there a way to have the equivalent of a mouseup fired for a single touch?

    Thanks,

    vivid.

  • eaglejohn said

    @Richard Is it possible to use this with a <img> or <a> tag? So you can click a button which erases the canvas?

  • venkata said

    hi thanks for this amazing code snippet but i noticed one problem on my android tablet. if anyone have solution for the same please let me know

    ## Problem: When i try to draw on my android tablet the code is working but there is lot of delay in the drawing.
    Can anyone please help me in this..
    thanks

  • 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!

  • Stylesie said

    @venkata
    I had the same problem with my Motorola Xoom, horrible delay when using this script.

    I recently upgraded to Ice Cream sandwich and the performance has improved dramatically.

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

    To prevent the line from getting "choppy", add
    context.lineCap="round";

    This worked for me.

  • Gauri said

    Hi,

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

Comments have been disabled for this content.