Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
I never fully recovered my workspace setup when I upgraded my laptop two years ago, and I still miss things today. If only I had known about Gitpod back then…
Earlier I explored some of the basic drawing methods available on the 2D context of the new canvas element in HTML5, moving on from there, I’ve put together another quick demo here (see image below) which lets the user scribble inside the canvas element.
HTML
The HTML for the page is simple enough, the key thing is obviously the canvas element:
<section id="wrapper"> <h1>HTML5 Demo</h1> <aside id="message"></aside> <article> <section class="block"> <h4>Drawing tools:</h4> <article> Line width: <input type="text" id="btnLinewidth" value="1"></input> pixels </article> <article> <input type="submit" id="btnClear" value="Clear"></input> </article> </section> <section class="block"> <h4>Canvas area:</h4> <canvas id="drawingCanvas" width="400" height="400" class="block" onSelectStart="this.style.cursor='crosshair'; return false;"></canvas> </section> </article> </section>
CSS
The css for the canvas element is as below:
#drawingCanvas { border-style: dotted; border-width: 1px; border-color: Black; cursor: crosshair; }
I want to use the ‘crosshair’ cursor whenever you’re in the canvas area, but the above css only works when the left mouse button is not held down (when by default, the cursor changes to the ‘text’ cursor) so in addition to the css I added an event handler to change the cursor on the select start event in the HTML mark up:
<canvas id="drawingCanvas" width="400" height="400" class="block" onSelectStart="this.style.cursor='crosshair'; return false;"></canvas>
Javascript
Canvas support detection
When the page finishes loading I first check if the browser supports the canvas element, again using the Modernizr library, and display a warning message if canvas is not supported:
$(document).ready(function () { // display a warning message if canvas is not supported if (!Modernizr.canvas) { $("#message").html("<p><b>WARNING</b>: Your browser does not support HTML5's canvas feature, you won't be able to see the drawings below</p>"); $("article").hide(); } else { initialize(); } });
Designing the interactions
The interaction I’m after here is such that you start drawing by clicking and holding down the left mouse button and the path is drawn as you move the mouse while still holding the mouse button, and you stop drawing by releasing the mouse button or moving the mouse out of the canvas area. So ultimately it all boils down to the standard onmousedown, onmousemove, onmouseup and onmouseout events.
There’s one more problem which we have to solve first though – how to resolve the x, y coordinates of the click in relation to the canvas (rather than the page itself). The problem is that mouse events are implemented differently in different browsers, so here’s a helper function which encapsulates that particular piece of logic:
// works out the X, Y coordinates of the click INSIDE the canvas element from the X, Y // coordinates on the page function getPosition(mouseEvent, element) { var x, y; if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) { x = mouseEvent.pageX; y = mouseEvent.pageY; } else { x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop; } return { X: x - element.offsetLeft, Y: y - element.offsetTop }; }
Mouse event handlers
First, we need to obtain references to both the canvas element and the 2D drawing context so we can use them later:
var element = document.getElementById("drawingCanvas"); var context = element.getContext("2d");
For the mouse down event, we need to first work out the x and y coordinates of the click inside the canvas then start our path at that point:
// start drawing when the mousedown event fires, and attach handlers to // draw a line to wherever the mouse moves to $("#drawingCanvas").mousedown(function (mouseEvent) { var position = getPosition(mouseEvent, element); context.moveTo(position.X, position.Y); context.beginPath(); // attach event handlers $(this).mousemove(function (mouseEvent) { drawLine(mouseEvent, element, context); }).mouseup(function (mouseEvent) { finishDrawing(mouseEvent, element, context); }).mouseout(function (mouseEvent) { finishDrawing(mouseEvent, element, context); }); }); // draws a line to the x and y coordinates of the mouse event inside // the specified element using the specified context function drawLine(mouseEvent, element, context) { var position = getPosition(event, element); context.lineTo(position.X, position.Y); context.stroke(); } // draws a line from the last coordiantes in the path to the finishing // coordinates and unbind any event handlers which need to be preceded // by the mouse down event function finishDrawing(mouseEvent, element, context) { // draw the line to the finishing coordinates drawLine(mouseEvent, element, context); context.closePath(); // unbind any events which could draw $(element).unbind("mousemove").unbind("mouseup").unbind("mouseout"); }
The approach I have taken here is to attach the event handlers for mouse move/up/out inside the mouse down event handler (which marks the start of the drawing process), these handlers are then unbound when mouse up or mouse out event occurs (which marks the end of the drawing process).
This approach removes the need for a global flag (don’t you just hate those by now!?) to track when the code needs to draw a line. It also makes sure that the logical start and end of the ‘drawing’ event (which as stated before, starts at mouse down and finishes at either mouse up or mouse out) matches the lifetime of the handlers required to handle this so that we don’t handle the mouse move/up/out events unless we need to.
To round things off, there are two more handlers needed to clear the canvas and to change the width of the lines being drawn:
// clear the content of the canvas by resizing the element $("#btnClear").click(function () { // remember the current line width var currentWidth = context.lineWidth; element.width = element.width; context.lineWidth = currentWidth; }); // change the line width $("#btnLinewidth").change(function (event) { if (!isNaN(event.target.value)) { context.lineWidth = event.target.value; } });
Oh and one more thing, you might have noticed that I’ve chained some of the method calls, such as this:
$(element).unbind("mousemove").unbind("mouseup").unbind("mouseout");
This is a simple optimization step to reduce the number of times jQuery needs to look through the DOM to identify matching elements. So that’s it, a simple HTML5 page to let you scribble in a canvas page :-)
Related posts:
Having fun with HTML5 – Canvas, part 1
Having fun with HTML5 – Canvas, part 3
Having fun with HTML5 – Canvas, part 4
Having fun with HTML5 – Canvas, part 5
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.
Pingback: Having fun with HTML5 – Canvas, part 3 | theburningmonk.com
Pingback: Having fun with HTML5 – Canvas, part 1 | theburningmonk.com
Pingback: Having fun with HTML5 – Canvas, part 4 | theburningmonk.com
Hi! I was wondering if you could help me out a little bit. so I have a canvas set up but I only want the cursor to change in a specific part of the canvas. Is there any way I could accomplish this?