A Simple Touchscreen Sketchpad using Javascript and HTML5
With the release of Windows 8, and the huge popularity of iPads and Android tablets, touchscreen is being built into more and more traditional PCs.
However, seeing dedicated touchscreen features on websites is still comparatively rare.
Here we’ll show you how to use some basic HTML and Javascript to add a simple touch-enabled sketchpad to a page.
We’re not trying to build a full-featured touchscreen app here – just a simple, very lightweight sketchpad, but it should be enough to show you some of the basic ideas behind touch support on the web.
If you are thinking of making a full HTML 5 touchscreen site or app, you might want to check out something like jQuery Mobile, however it’s worth going through the pure Javascript version here to get an understanding of the interactions between the HTML 5 canvas, and the mouse and touchscreen functions.
Compatibility note: We going to use the most common “Touch Events” approach here which is supported by all browsers on iOS and Android. Unfortunately, this won’t work directly on Windows 8 touchscreen when using Internet Explorer, however, it can be adapted relatively easily using the alternative “Pointer Events” model.
See the “Supported browsers” note towards the end of the article for more information.
Blank paper
First, we need something to draw on. Let’s start with a web-page with some text, and an area beside it for sketching. We can use the HTML 5 canvas tag to create our sketchpad area.
Edit the HTML for your page, add the canvas tag with an id name of your choice, and change the dimensions to fit your layout.
<canvas id="sketchpad" width="400" height="300"></canvas>
This creates our blank canvas. Now let’s try to make it into a working sketchpad. We can do this using the mouse first, then add touchscreen support later.
So what happens in a sketchpad?
When the cursor/pointer is over the sketchpad, and a mouse button pressed, then we want to draw something at that location.
Since our drawing action will take place when the mouse button is pressed down, we can put all of our code into a function that’s called when this happens.
We need two things:
- A function to draw a dot (filled circle) at the specified location on the canvas
- A way to call this function when the mouse button is being held down, and give it the current position of the mouse
Let’s try to do the drawing function first.
We need to write some small Javascript code to do this.
<script type="text/javascript">
// Get the specific canvas element from the HTML document
var canvas = document.getElementById('sketchpad');
// If the browser supports the canvas tag, get the 2d drawing context for this canvas
if (canvas.getContext)
var ctx = canvas.getContext('2d');
// Draws a dot at a specific position on the supplied canvas name
// Parameters are: A canvas context, the x position, the y position
function drawDot(ctx,x,y) {
// Let's use black by setting RGB values to 0, and 255 alpha (completely opaque)
r=0; g=0; b=0; a=255;
// Select a fill style
ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
// Draw a filled circle
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
</script>
Now we can draw on the canvas at a location of our choice, using the drawDot function.
When to Draw
When do we want to draw a dot on the sketchpad?
There are two cases (assuming the mouse is positioned over the sketchpad):
- Immediately after the mouse button is pressed
- Immediately after moving the mouse with the button held down
There is a slight complication here, since we can’t directly check if the mouse button is being held down using Javascript.
We have to keep track of when the button is pressed and released ourselves, in order to determine whether it’s currently being held down.
Tracking the mouse
In fact, we have 3 useful triggers or “events” to help us keep track of the mouse:
- mousedown event – Activated once whenever the (left) mouse button is pressed.
- mouseup event – Activated once whenever the (left) mouse button is released.
- mousemove event – Activated each time that the mouse is being moved, regardless of the button state.
So, we will need to keep track of the mouse button status by using the mouseup and mousedown triggers.
Additionally, we can use mousemove to do something useful when the mouse is being moved.
What happens with these events normally?
Nothing happens – We will need to add event handlers to tell the browser to do something when that specific event is triggered.
Event handlers
What each handler will do is simply call a function of our choice immediately after the event is triggered. So we should have one function for each event.
Let’s call them sketchpad_mouseDown, sketchpad_mouseUp and sketchpad_mouseMove.
We’ll also use our own getMousePos function to get the current co-ordinates of the mouse pointer when it’s moved.
<script type="text/javascript">
// Define some variables to keep track of the mouse status
var mouseX,mouseY,mouseDown=0;
function sketchpad_mouseDown() {
mouseDown=1;
drawDot(ctx,mouseX,mouseY);
}
function sketchpad_mouseUp() {
mouseDown=0;
}
function sketchpad_mouseMove(e) {
// Update the mouse co-ordinates when moved
getMousePos(e);
// Draw a pixel if the mouse button is currently being pressed
if (mouseDown==1) {
drawDot(ctx,mouseX,mouseY);
}
}
// Get the current mouse position relative to the top-left of the canvas
function getMousePos(e) {
if (!e)
var e = event;
if (e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
else if (e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
}
</script>
So now we have our 3 event handlers each performing a function –
- sketchpad_mouseDown – registers a mouse click and also draws a pixel at the current position.
- sketchpad_mouseUp – simply de-registers the mouse click, so we know that the button is no longer being pressed.
- sketchpad_mouseMove – checks to see if the mouse button is currently down, and if so, draws something at the current location.
- getMousePos – updates our mouseX and mouseY variables with the current position of the mouse.
All we have to do now is attach our event handlers to the events themselves.
canvas.addEventListener('mousedown', sketchpad_mouseDown, false);
canvas.addEventListener('mousemove', sketchpad_mouseMove, false);
window.addEventListener('mouseup', sketchpad_mouseUp, false);
You might notice that we are using addEventListener with the canvas element in the first two cases, and then with “window” for the mouseup event.
This is because for mouse downs and mouse moves, we’re only interested when these happen over our sketchpad canvas.
However, someone could click the mouse button on the canvas, then keep it held down, but move the mouse to another part of the web page. If they then release the mouse button, we won’t see this event happening if we’re just looking at the canvas, and our mouse button tracking can become confused, and not register the button being released.
In order to avoid this, we will listen in the entire window for mouse up events, so that we can track them correctly even if they happen outside of our sketchpad box.
Note: You can also use “document.body” instead of window, however since our example is being displayed in this post within an iframe, using “window” should capture the mouseup event outside of the iframe, whereas using “document.body” won’t.
Putting it all together
Here’s what our mouse-based sketchpad looks like right now (try it!):
(Look further down this article for the full touchscreen version)
… and here’s the complete web-page code for the mouse-based version:
sketchpad-mouse.html (text file)
<html>
<head>
<title>Sketchpad</title>
<script type="text/javascript">
// Variables for referencing the canvas and 2dcanvas context
var canvas,ctx;
// Variables to keep track of the mouse position and left-button status
var mouseX,mouseY,mouseDown=0;
// Draws a dot at a specific position on the supplied canvas name
// Parameters are: A canvas context, the x position, the y position, the size of the dot
function drawDot(ctx,x,y,size) {
// Let's use black by setting RGB values to 0, and 255 alpha (completely opaque)
r=0; g=0; b=0; a=255;
// Select a fill style
ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
// Draw a filled circle
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
// Clear the canvas context using the canvas width and height
function clearCanvas(canvas,ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Keep track of the mouse button being pressed and draw a dot at current location
function sketchpad_mouseDown() {
mouseDown=1;
drawDot(ctx,mouseX,mouseY,12);
}
// Keep track of the mouse button being released
function sketchpad_mouseUp() {
mouseDown=0;
}
// Keep track of the mouse position and draw a dot if mouse button is currently pressed
function sketchpad_mouseMove(e) {
// Update the mouse co-ordinates when moved
getMousePos(e);
// Draw a dot if the mouse button is currently being pressed
if (mouseDown==1) {
drawDot(ctx,mouseX,mouseY,12);
}
}
// Get the current mouse position relative to the top-left of the canvas
function getMousePos(e) {
if (!e)
var e = event;
if (e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
else if (e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
}
// Set-up the canvas and add our event handlers after the page has loaded
function init() {
// Get the specific canvas element from the HTML document
canvas = document.getElementById('sketchpad');
// If the browser supports the canvas tag, get the 2d drawing context for this canvas
if (canvas.getContext)
ctx = canvas.getContext('2d');
// Check that we have a valid context to draw on/with before adding event handlers
if (ctx) {
canvas.addEventListener('mousedown', sketchpad_mouseDown, false);
canvas.addEventListener('mousemove', sketchpad_mouseMove, false);
window.addEventListener('mouseup', sketchpad_mouseUp, false);
}
}
</script>
<style>
/* Some CSS styling */
#sketchpadapp {
/* Prevent nearby text being highlighted when accidentally dragging mouse outside confines of the canvas */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.leftside {
float:left;
width:220px;
height:285px;
background-color:#def;
padding:10px;
border-radius:4px;
}
.rightside {
float:left;
margin-left:10px;
}
#sketchpad {
float:left;
border:2px solid #888;
border-radius:4px;
position:relative; /* Necessary for correct mouse co-ords in Firefox */
}
</style>
</head>
<body onload="init()">
<div id="sketchpadapp">
<div class="leftside">
Simple mouse-based HTML5 canvas sketchpad.<br/><br/>
Draw something by holding down the mouse button or using single clicks.<br/><br/>
On a touchscreen, tapping the area will register as a single mouse click.<br/><br/>
<input type="submit" value="Clear Sketchpad" onclick="clearCanvas(canvas,ctx);">
</div>
<div class="rightside">
<canvas id="sketchpad" height="300" width="400">
</canvas>
</div>
</div>
</body>
</html>
Note that we have moved the initialisation of the canvas context and event handlers into a separate “init()” function that is called just after the page has finished loading (using “<body onload..>”).
We’ve also added a very simple “Clear” button which calls a function with a single line of code to clear the canvas.
Prevent nearby text being accidentally selected
You might notice that we’ve used this block of styling in the CSS of the div surrounding the sketchpad:
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
This is due to the fact that when you have a large area on the screen that involves dragging the mouse across, the mouse cursor can sometimes stray outside of this area.
When this happens, because the mouse button is being held down, it’s interpreted by the web browser as a request to select areas of text. This produces a distracting flash of highlighting on the page.
To prevent this happening, we can just use the above user-select statements in the relevant CSS area to prevent text surrounding the sketchpad from being “selectable”.
Adding Touchscreen support
If you just want to play around with a touchscreen interface, it might seem like a lot of effort adding all of the mouse support above.
However, the touchscreen code is very similar to the mouse-based sketchpad code, so it’s useful to understand how the mouse version works before looking at touchscreen. Also, in most cases you’ll want to have a functioning mouse version working as a fallback.
Touch events
If you try the current mouse-based version on an iPad, it’s not very appealing. Moving your finger over the sketchpad scrolls it, and you can just about make a blot appear if you press on a single area.
This is because we’re not yet listening to the specific events that are being triggered when the canvas area is touched.
We are getting some limited interaction with a single tap on the sketchpad – this is because the tap is also being interpreted as a mouseclick, and so it will (eventually) fire the mousedown event handler.
However, there is a 300ms delay when using the mouse events like this on a touchscreen device.
This is because the device is waiting to see if the single-tap is actually a double-tap. When we add our touch event handlers, we won’t need to worry about this quirk, since the touch events are triggered instantly after touching the screen.
Just like the mouse version, we have access to at least 3 useful touch-based events:
- touchstart event – Activated whenever a touch is registered on the associated area.
- touchend event – Activated whenever a touch is released from the associated area.
- touchmove event – Activated whenever a touch movement is detected in the associated area.
Let’s look at the code for dealing with these events.
We’re going to keep this code separate from the mouse-handling code, which leads to some bloat, but gives a bit more flexibility in cases where you need to perform a specific action only in response to a touch event.
Also, the code is a little simpler, since we don’t need to keep track of anything like the mouse button status – when a touchmove event is fired, we can already assume the equivalent of “the mouse button is being pressed” is happening.
We also don’t really need to do anything with the touchend event in this case, however you might need to add it in a more complex application to perform an event when a finger is lifted off the screen – for example to update some values. In this case, just copy the format of the touchstart event handler.
Note also that we’re just going to deal with non-multitouch support for the moment.
If you are working with a complex multitouch site or potential HTML app, you might want to look into something like hammer.js.
Here are the functions that we’re going to trigger on touch events.
<script type="text/javascript">
// Define some variables to keep track of the touch position
var touchX,touchY;
function sketchpad_touchStart() {
getTouchPos();
drawDot(ctx,touchX,touchY,12);
// Prevents an additional mousedown event being triggered
event.preventDefault();
}
function sketchpad_touchMove(e) {
// Update the touch co-ordinates
getTouchPos(e);
// During a touchmove event, unlike a mousemove event, we don't need to check if the touch is engaged, since there will always be contact with the screen by definition.
drawDot(ctx,touchX,touchY,12);
// Prevent a scrolling action as a result of this touchmove triggering.
event.preventDefault();
}
// Get the touch position relative to the top-left of the canvas
// When we get the raw values of pageX and pageY below, they take into account the scrolling on the page
// but not the position relative to our target div. We'll adjust them using "target.offsetLeft" and
// "target.offsetTop" to get the correct values in relation to the top left of the canvas.
function getTouchPos(e) {
if (!e)
var e = event;
if (e.touches) {
if (e.touches.length == 1) { // Only deal with one finger
var touch = e.touches[0]; // Get the information for finger #1
touchX=touch.pageX-touch.target.offsetLeft;
touchY=touch.pageY-touch.target.offsetTop;
}
}
}
</script>
Prevent screen scrolling on touch-enabled elements
A very important part of adding touch support to a page is working out which elements should be immune to scrolling the page by swiping up or down.
There are some ergonomic considerations here – you don’t want to confuse the user by having large swathes of the screen un-scrollable. The key is to have intuitive, reasonably sized areas that perform some action based on touch, that don’t also scroll the page.
To prevent scrolling on individual page elements, we need to call the Javascript preventDefault() function at the end of our touchmove handler. You can see this in the sketchpad_touchMove function above.
Remember that we still have to attach our touch handlers above to the correct elements on the page.
We can do this as follows:
canvas.addEventListener('touchstart', sketchpad_touchStart, false);
canvas.addEventListener('touchmove', sketchpad_touchMove, false);
Note that we’re only attaching the event listeners to the canvas element.
If you decide to attach these listeners to a larger part of the page, or even the entire document, you would probably need to be careful in managing use of preventDefault() to stop swipe-scrolling – for example, you might only trigger it for certain touch co-ordinates or boundaries.
Also, even though we’re only capturing touchmove in relation to our canvas element, it will continue firing even if you drag outside of the canvas area, so be aware that the touch co-ordinates you get at this stage can be outside the bounds of the canvas.
Note that we’re using the most general form of retrieving the current “touch list” by using e.touches in getTouchPos().
You could also use something like e.targetTouches or e.changedTouches, which can give a slightly different list. This is more important for multi-touch applications though, compared to single-touch support.
Complete version
Now we have full touchscreen and mouse support:
… and here’s the complete code for the final version:
sketchpad-touch.html (text file)
<html>
<head>
<title>Sketchpad</title>
<script type="text/javascript">
// Variables for referencing the canvas and 2dcanvas context
var canvas,ctx;
// Variables to keep track of the mouse position and left-button status
var mouseX,mouseY,mouseDown=0;
// Variables to keep track of the touch position
var touchX,touchY;
// Draws a dot at a specific position on the supplied canvas name
// Parameters are: A canvas context, the x position, the y position, the size of the dot
function drawDot(ctx,x,y,size) {
// Let's use black by setting RGB values to 0, and 255 alpha (completely opaque)
r=0; g=0; b=0; a=255;
// Select a fill style
ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
// Draw a filled circle
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
// Clear the canvas context using the canvas width and height
function clearCanvas(canvas,ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Keep track of the mouse button being pressed and draw a dot at current location
function sketchpad_mouseDown() {
mouseDown=1;
drawDot(ctx,mouseX,mouseY,12);
}
// Keep track of the mouse button being released
function sketchpad_mouseUp() {
mouseDown=0;
}
// Keep track of the mouse position and draw a dot if mouse button is currently pressed
function sketchpad_mouseMove(e) {
// Update the mouse co-ordinates when moved
getMousePos(e);
// Draw a dot if the mouse button is currently being pressed
if (mouseDown==1) {
drawDot(ctx,mouseX,mouseY,12);
}
}
// Get the current mouse position relative to the top-left of the canvas
function getMousePos(e) {
if (!e)
var e = event;
if (e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
else if (e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
}
// Draw something when a touch start is detected
function sketchpad_touchStart() {
// Update the touch co-ordinates
getTouchPos();
drawDot(ctx,touchX,touchY,12);
// Prevents an additional mousedown event being triggered
event.preventDefault();
}
// Draw something and prevent the default scrolling when touch movement is detected
function sketchpad_touchMove(e) {
// Update the touch co-ordinates
getTouchPos(e);
// During a touchmove event, unlike a mousemove event, we don't need to check if the touch is engaged, since there will always be contact with the screen by definition.
drawDot(ctx,touchX,touchY,12);
// Prevent a scrolling action as a result of this touchmove triggering.
event.preventDefault();
}
// Get the touch position relative to the top-left of the canvas
// When we get the raw values of pageX and pageY below, they take into account the scrolling on the page
// but not the position relative to our target div. We'll adjust them using "target.offsetLeft" and
// "target.offsetTop" to get the correct values in relation to the top left of the canvas.
function getTouchPos(e) {
if (!e)
var e = event;
if(e.touches) {
if (e.touches.length == 1) { // Only deal with one finger
var touch = e.touches[0]; // Get the information for finger #1
touchX=touch.pageX-touch.target.offsetLeft;
touchY=touch.pageY-touch.target.offsetTop;
}
}
}
// Set-up the canvas and add our event handlers after the page has loaded
function init() {
// Get the specific canvas element from the HTML document
canvas = document.getElementById('sketchpad');
// If the browser supports the canvas tag, get the 2d drawing context for this canvas
if (canvas.getContext)
ctx = canvas.getContext('2d');
// Check that we have a valid context to draw on/with before adding event handlers
if (ctx) {
// React to mouse events on the canvas, and mouseup on the entire document
canvas.addEventListener('mousedown', sketchpad_mouseDown, false);
canvas.addEventListener('mousemove', sketchpad_mouseMove, false);
window.addEventListener('mouseup', sketchpad_mouseUp, false);
// React to touch events on the canvas
canvas.addEventListener('touchstart', sketchpad_touchStart, false);
canvas.addEventListener('touchmove', sketchpad_touchMove, false);
}
}
</script>
<style>
/* Some CSS styling */
#sketchpadapp {
/* Prevent nearby text being highlighted when accidentally dragging mouse outside confines of the canvas */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.leftside {
float:left;
width:220px;
height:285px;
background-color:#def;
padding:10px;
border-radius:4px;
}
.rightside {
float:left;
margin-left:10px;
}
#sketchpad {
float:left;
height:300px;
width:400px;
border:2px solid #888;
border-radius:4px;
position:relative; /* Necessary for correct mouse co-ords in Firefox */
}
#clearbutton {
font-size: 15px;
padding: 10px;
-webkit-appearance: none;
background: #eee;
border: 1px solid #888;
}
</style>
</head>
<body onload="init()">
<div id="sketchpadapp">
<div class="leftside">
Touchscreen and mouse support HTML5 canvas sketchpad.<br/><br/>
Draw something by tapping or dragging.<br/><br/>
Works on iOS, Android and desktop/laptop touchscreens using Chrome/Firefox/Safari.<br/><br/>
<input type="submit" value="Clear Sketchpad" id="clearbutton" onclick="clearCanvas(canvas,ctx);">
</div>
<div class="rightside">
<canvas id="sketchpad" height="300" width="400">
</canvas>
</div>
</div>
</body>
</html>
Touchscreen browsers try to emulate mouse events
It’s worth clarifying an additional point at this stage – in order to try to get maximum compatibility with existing websites and their mouse event handling, browsers on touchscreen devices such as Mobile Safari will try their best to emulate a mouse when you tap on any page element that is responsive to mouse events, such as a link, or a div or canvas listening for mousedown, mouseup etc.
So unless we prevent this behaviour, we might get a double-acknowledgement when tapping on the sketchpad for example – the first event trigger will be a touchstart, followed shortly after by a mousedown.
To stop this happening, we simply use the preventDefault() call when we’re finished with our touchstart, in order to stop any further events from being triggered due to that specific touch action.
You might run into some bugs with this on older Android browsers, however, especially if you’re also using more complex interactions with touchend, so it may be worth fully testing this behaviour on a touchscreen device.
Also, only use preventDefault() in this context when you’re sure that you have completed whatever the action the user wanted.
If you listened for touchstart on the entire document page for example, and issued a preventDefault() regardless of where/what the user touched, you may find that they are no longer able to tap on links on the page, since you’ve stopped the further issuing of the “emulated” mousedown or mouseclick on the link.
So just use preventDefault() when you’re sure that you’ve covered all the potential user interaction within the touch events associated with that element.
Supported browsers/devices
The HTML 5 canvas element works in all modern browsers, including mobile/tablet. With Internet Explorer, it’s supported from version 9 upwards. The touch events (touchstart, touchmove..) work on all mobile/tablet browsers. The situation with desktop browsers is a little more involved.
IE has an alternative system called Pointer Events that is supported from IE 10 upwards. The problem here is that other browsers have yet to add support, but it’s expected to happen throughout 2014 and onward.
Firefox supports touch events, but due to a bug filed in bugzilla, they have been turned off by default in the past few releases. They can be re-enabled in the browser. Further details are here.
It’s still worth using the Touch events version now however, since it supports all current and legacy Android and iPad users for example, as well as Chromebook and other touchscreen users on desktops/laptops.
As more non-mobile users become touchscreen enabled, pressure will undoubtedly grow on browser developers to get everything working smoothly on the desktop too.
2018 update – Although most desktops browsers now support Pointer Events, mobile and especially iOS support is not there – see current browser support for more details.
Upgrades
Once you’ve got to grips with the basic framework for the sketchpad, it’s easy to add features!
Change the pen size
Replace every instance of “12” in called to the DrawDot() function with a variable of your own.
You can then add images or buttons to the page which change the value of this variable when clicked.
Change the colours or add a background
Following the same method as changing the pen size, simply modify DrawDot() to use your own RGBA (red,green,blue,alpha/opacity) values.
Change these values based on some interaction with an on-screen widget (or use a counter to give a funky rainbow effect?).
To add a background image to the canvas, simply add a “background-image:” line to the CSS code for the canvas (our example canvas is called #sketchpad)
Update:
Here’s an example of a Sketchpad with a background image and a counter for changing the hue of the pen. Instead of using the RGB colour scheme when drawing the dot, we’re now using HSL which makes it much easier to change the hue.
Check out the HSL Calculator here for a visual representation of what the different H,S and L values mean.
…and here’s a link to the background image …have fun!
Draw connected lines instead of dots
You might have noticed that if you move the mouse or touch position too fast, the rendering can’t keep up and you get some spaces between dots.
An alternative is to create a DrawLine function, similar to DrawDot, except that it keeps track of the last x,y position as well as the current one.
Now instead of using ctx.arc to draw a circle, use ctx.moveTo to position the start of the line at the old co-ordinates, then ctx.lineTo to draw the line to the new (current) co-ordinates (example here).
This changes the look and feel of the sketchpad, so it may or may not be an improvement on the “dot” method.
Update: Here is a working example of the sketchpad using lines (source code is here).
Note that we’ve added lastX and lastY variables, and set them to -1 at the start. These will be our starting positions when we draw a line, but since we don’t know where the first line will start from, we will use the -1 value to indicate that the current position is invalid, or unknown. There is no special significance to using -1, it could be any other co-ordinate value that won’t appear in normal use.
At the start of our new DrawLine function, we check if lastX is invalid (set to -1).
This will be the case the first time a line is drawn, or if the “pen” (mouse or finger) is not touching the canvas. If so, we will just set lastX and lastY to the current position, then the next time we call drawLine, we will be able to correctly draw a line with two valid sets of co-ordinates.
This introduces a slight bit of added complexity, since we need to invalidate the lastX and lastY co-ordinates any time the pen is lifted from the canvas. Otherwise, we would continue drawing a line from the last position, even when someone has lifted the “pen” and decided to draw in a different part of the canvas.
The solution is simply to set lastX and lastY to -1 in the mouseUp function, and also in a new touchEnd function:
function sketchpad_mouseUp() {
mouseDown=0;
// Reset lastX and lastY to -1 to indicate that they are now invalid, since we have lifted the "pen"
lastX=-1;
lastY=-1;
}
function sketchpad_touchEnd() {
// Reset lastX and lastY to -1 to indicate that they are now invalid, since we have lifted the "pen"
lastX=-1;
lastY=-1;
}
…and also add the new touchEnd event listener to our init() function:
canvas.addEventListener('touchend', sketchpad_touchEnd, false);
Line style
One final point we need to look at when drawing lines is setting the line “cap” style. This is simply how the end of the line looks like, but if it’s not changed from the default setting of “butt”, the lines can look disjointed when drawing, since not enough pixels are drawn to connect adjacent lines at different angles.
We can do this in the drawLine() function as follows:
ctx.lineCap = "round";
You can try commenting out this line to see what happens without it. You can also use “ctx.lineJoin” to change how it looks when two lines join, but it’s not relevant to our sketchpad since we are only drawing a single line each time between two co-ordinates, instead of a group of lines at once.
Want to let users submit their pictures after drawing them?
Try using the canvas toDataURL() function. This will convert the entire canvas area into a base64 encoded .PNG image.
You can then use this data in a hidden form field with a submit button to post the encoded image to your server. You’ll also need to strip out some of the extraneous formatting and convert back from base64 to a binary image on the server side.
Check out the answer here using PHP by user568021.
Also see the image saving demo code below, which implements most of the steps to get the image on to the server – you would just need to modify the PHP script to save it (using e.g. the PHP function file_put_contents() ) instead of sending it back to the user.
I just want to save the Sketchpad as an image?
This is surprisingly a little more difficult than it first appears. There are a few different ways of saving the sketchpad, but they all start with converting the sketchpad data to an image format such as PNG first.
The difficulty is in sending this image to the browser as a download. If you’re OK about first sending the image to a script on a server, then this is the best way for the end-user (apart from a small delay while uploading and downloading the image.
Here is a demo page, and here is the HTML source.
Here is the source for the PHP script (“save-image.php”) we will use.
This is what happens:
- A “Save Image” button is added to the Sketchpad page. This button is part of a HTML form.
- When clicked, a function is called, let’s call it saveImageRemote().
- This function converts the canvas data into an image (PNG) format using “toDataURL”.
- The image data is added to a hidden variable in the form (“save_remote_data”) and the form is submitted.
- A PHP script receives the form data, and converts it into a binary image. Before sending the image back to the browser, it adds Content headers to specify that it’s a downloadable file.
- The browser receives the data back from the script and starts downloading the image.
Why do we have to go through the strange steps of uploading the image to a server just to immediately download it again?
It’s because we can’t easily force the browser to accept an image download, unless that download comes from a remote server.
There are ways of forcing the image data into the current browser page (for example, using “window.location.href=image” in the answers here), however it doesn’t work very well – there is no .PNG file extension added, and the end-user can’t simply click on the file to open it.
By sending our image data to a remote script, we can add the content headers that tell the browser that it should download the file, and specify other data such as filename and filetype. The disadvantage of this approach is that it doesn’t work offline, and there may be a slight delay in uploading/downloading for large sketchpad images.
Note that iOS / Mobile Safari won’t process image downloads at all – they simply display within the browser, and the end-user has to long-tap to save them regardless.
I need to be able to save it offline
Looking at the previous example, we can also add a “View Image” button which displays the Sketchpad as a plain image in a new browser tab.
Alternatively, you can provide a thumbnail image of the sketchpad that updates every time the mouseup or touchend events are triggered. We do this in the example above using the “updateImageLocal()” function.
The user can then long tap or right-click on this thumbnail to save the image (the image will be saved as the full size version).
I would like to have more than one Sketchpad on the screen
There are a number of ways of doing this. One way is to create separate Javascript objects for each sketchpad (along with addition HTML canvas elements). It’s more complicated, but doesn’t require much extra code. We will need to get rid of the global canvas and ctx variables since they only allow for a single canvas and canvas context to be defined.
We will need to move these into a new ‘sketchpad()‘ function. We will also need to move the event handlers in here.
Check out a working example here, and adjusted source code here.
In our new init() function, we’re creating one or more sketchpads using our new sketchpad() function. This will mean that each separate sketchpad gets it’s own canvas and context variables.
To make this work properly, we need to preface any variables and functions that need to be kept local to either sketch1 or sketch2 (in our example), with “this.“.
“this” is simply a reference to either sketch1 or sketch2. When we create sketch1 with the line sketch1 = new sketchpad(‘sketchpad1’) then any reference to “this” is equivalent to referencing the sketch1 object. When we create sketch2, “this” is a reference to sketch2.
This means that each set of context and canvas values don’t get shared or mixed up between sketch1 and sketch2.
The last modification needed is to add “.bind(this)” when we’re adding our event listeners. This ensures that the functions that are called when the event triggers, also use the correct value of “this“.
In other words, when we refer to “this.ctx” in the drawDot() line in sketchpad_touchStart(), it should use the same value of “this” as in the main “sketchpad()” function, and therefore refer to the correct canvas context.
The “.bind(this)” is used because when an event is triggered, we don’t normally easily have access to whatever the value of “this” is between where the event handler was added, and the function that gets called when it triggers. See here for more details. Bind is a relatively new addition to Javascript that is available in IE9 and any new browsers.
We can also now easily clear a specific canvas by simply using “sketch1.clearCanvas();” for example. Since it’s simply a normal function and not a browser event, we don’t need to deal with the bind keyword.
The canvas sketchpad should give you a good basis for understanding interactions between mouse events, touchscreen events and the canvas element, even if you decide to use an alternative framework later.
Did you find it useful or informative?
Were you able to use some of the ideas in projects of your own?
Let us know in the comments section below.
How to insert an eraser in the sketchpad?
Is there a way to resize the canvas to be responsive to the device the user is on. I can resize things with max widths, but then the drawing is all offset and doesn’t work.
I’d like thing to easily resize for a phone, tablet or desktop.
Also for saving image in coldfusion setting:
Just pass the canvas through form method and handle with this below.
Never mind. Drawing rectangle did the trick.
ctx.fillStyle = ‘green’;
ctx.fillRect(0, 0, canvas.width, canvas.height);
Thanks for the useful tip!
I think this is great! well explained, but I have the problem that the PNG image when saved (downloaded) to a mobile phone, shows a black rectangle when opened (and shows the correct image in the miniature!)
Is there a simple way to add a background to the png so it´s not transparent, or a way to save the image in Jpg?
I searched for 2 days but no option worked.
very useful tutorial
i wonder how i can make it straight line like _____ not dots style
thanks
+1. I am also wondering this. Will try tomorrow to find a solution.
Did you see the section above called “Draw connected lines instead of dots”?
It should have a link to a working example and the code for it.
I did, but I think the above poster was looking for exact straight lines. I found quick solution using fabric.js that suited my use case.
https://codepen.io/franklynroth/pen/OwvLRr?editors=1010
Still going to look at this tomorrow, its an awesome resource.
Oh ok, cool 🙂
Hi zipso,
This tutorial is worth 10/10 marks. Absolutely useful.
I have one more question: Instead of downloading the PNG file, I would like to make a button for users to immediately submit their canvas as a comment.
How can I do that?
And one small thing, where should I put the PHP script? It’s a file in the website’s root folder, or it’s a part of index.php file?
Thank you very much.
Hi, glad that you found it useful!
How to do this depends on the comment system of the website, but I guess that you are talking about using your own comment system instead of something like a WordPress site.
To do this, you can use the Javascript and PHP code in the section “I just want to save the Sketchpad as an image?“.
You just need to change the PHP script slightly. It’s usually better to have it as a separate file, in the example above it’s in a folder on the server called “sketchpad” and the file is called save-image.php, but you can name it whatever you like as long as you change the “action” line in the HTML to match the file location:
So that PHP script sends the file back to the user as a download. You want to save the image on the server instead, so you would need to get rid of these lines:
and replace them with something like:
This will save the sketchpad as an image file with a unique filename, like for example, sketchpad-5b1bf54ee0f6c.png
If you were going to use this in a serious application, be careful with accepting any user input like this on the server, since it can be a security risk.
So you may also want to do some further checks on the $data before saving it with file_put_contents, to check that it’s a real image and not for example, some HTML code etc.
Now to display the sketchpads as comments, have some PHP code on a page (or it can also be in an index.php file) that finds all the sketchpad files in the current directory and displays them, it could be something like this:
If you want to store commenter name etc. you would need to adapt the above to save this info in a separate text file perhaps, for example in the save-image.php file, you might add the line
..then write out some other info like name etc. submitted from the form to that text file.
Hi,
This is simply amazing. Is there a way in which we can draw the line on top of a graph/chart and get the graph coordinates? Any leads here would be extremely helpful.
Thanks,
Mohil
Well you can easily get the current x,y position of the dot or line being drawn. So if you have a graph as a background image underneath the canvas, you would need to first convert the canvas x,y position to an x,y position on the graph.
You can do this by finding the canvas position of the origin of the graph, or the “0,0” point on the graph. For example, if the graph 0,0 point is at the very bottom-left of the canvas, then it’s canvas position would be 0,300.
So if you’re looking for the values graph_x and graph_y, you would need to do something like:
graph_x=x-origin_x;
graph_y=origin_y-y;
Where origin_x and origin_y are the canvas co-ordinates of the graph origin position, i.e. in the previous example, 0 and 300.
You would then need to scale graph_x and graph_y by a value that represents the correspondence between the pixel length of the axis and the graph value at the end of the axis:
graph_x=graph_x*x_scale;
graph_y=graph_y*y_scale;
Where x_scale is the max value on the x axis of the graph, divided by the length of the x axis in canvas pixels.
In other words, for a graph with e.g. a max value of 200 on the x axis and 100 on the y axis, and where each axis is 280 canvas pixels in length or height, you would use:
x_scale=200/280;
y_scale=100/280;
See the heading on the page “Change the colours or add a background” which was updated to show a sketchpad with a background image. You just need to add, for example, background-image:url(filename.jpg) to the CSS for the canvas.
This example is exactly what I was looking for.
Thank you!
Hi really good work on this. It seems like the sketchpad using lines does not work on mobile. At least iOS devices. Any fixes you can suggest to make this work?
Can we hide the text on canvas.
Is there any code for redo after we clear the drawpad
Can you tell me how can i change different colors to the sketch pad i draw using colorpicker slider????
I’ve been having trouble finding a tutorial as well put together as this. Thank you so much. Trying to create a custom note taking app for myself. Thanks again!!!
I’m using IPhone 5 the draw line on touchscreen works fine on dot both tablet and PC but i can only draw line but not dot on my Ipone why?
This is super.
I’ve added the ability to drag the sketch pad around and am working to dynamically resize it.
I would like to be able to programatically add and remove new sketchpads, ideally with a button, in a similar way to this post, but utilising the awesome sketchpad you’ve done!
http://theburningmonk.com/2011/01/creating-a-sticky-note-app-with-html5-css3-and-javascript/
Cheers,
Nick
So, I’m calling this and it creates new sketch pads, but I’m getting a few problems:
– can only draw on the most recent sketch pad,
– can only clear the very first sketch pad.
function NewSketch() {
canvcnt = canvcnt + 1; // counter to track number of sketch pads
newcanvid = “sketchpad” + canvcnt;
var div = document.getElementById(‘sketch’);
div.innerHTML = div.innerHTML + ”;
sketchstr = “sketch” + canvcnt;
sketchstr = new sketchpad(newcanvid);
}
It’s hard to tell what’s happening just from this code snippet, however I’m guessing that the “sketch” div is where you have your multiple sketchpads.
There seems to be some code missing around the div.innerHTML line, presumably here you want to create the string containing the HTML that will add the new <canvas> element to the sketch div.
I think you should check what is actually added to the sketch div at this stage (using e.g. browser Inspect function or a Javascript alert() containing the HTML to be inserted), and see if the format of the canvas elements outputted is consistent with the previous example, i.e.
<canvas id="sketchpad1" class="sketchpad" height="300" width="400"></canvas>
<canvas id="sketchpad2" class="sketchpad" height="300" width="400"></canvas>
Hi,
Thanks for the reply, apologies it looks like. Y copy and paste didn’t get the full line.
You’re exactly right though,
div.innerHTML = div.innerHTML + ”;
The IDs are correct in that the numbering matches ok, my test page is at: http://patientpathfinder.co.uk/nick/wallboard2.html
Seem to have stopped it working on iPad somehow but does work as described from laptop, using Chrome.
Cheers
Copy and paste fail again, mustn’t like the code formatting! Please check my source
Did.innerHTML was causing problems with destroying eventlisteners, so used insertAdjacentHTML instead.
There’s a conversation here.
Still have some work to do with ClearCanvas, but made good progress so far!
Convo here: http://stackoverflow.com/questions/5677799/how-to-append-data-to-div-using-javascript
Hi.
A very good job! Tahnk you.
Just a question.
How i can set up width and height according to window client window dimension?
How would you add pressure? Thanks
This is not standardised so there are slightly different ways of doing it based on the platform.
I can’t test this right now, but you would probably add a variable like “touchForce” in the same way “touchX” and “touchY” are used.
Then in the “getTouchPos” function, after the “touchY=…” you might try adding something like:
touchForce=touch.force; // Firefox ?
or
touchForce=touch.webkitForce; // iOS
You could now change the pen colour (or something similar) while taking into account the touchForce value.
Also see these pages for more details:
https://developer.mozilla.org/en-US/docs/Web/API/Touch/force
https://developer.mozilla.org/en-US/docs/Web/API/Force_Touch_events
Also a touch force Javascript library:
http://pressurejs.com/
Can you please help me to add a background image to the canvas ?
you’r the best bro tnx
????????????
tried a lot of time plz just watch your own line draing js code and guide as soon as possible shaal be greatful to you….
can you add your own js file plz i have copied that
how can I change the lineWidth plzzzzzzzzzzzzzzzzz guide as soon as possible how can we change lineWidth by buttons..
The line thickness is set by the “size” parameter any time drawDot (or drawLine) is called. In the examples, this is hard-coded to the value of “12”, e.g.:
drawDot(ctx,touchX,touchY,12);
You would need to replace this “12” value with your own size variable, e.g. called linesize, any time drawDot (or drawLine) is called.
So it might look something like:
drawDot(ctx,touchX,touchY,linesize);
You would need to set linesize to some initial value at the start:
var linesize=12;
(for example, after the line var touchX,touchY;)
You would then need to add buttons on the page to change the linesize variable when pressed.
Take a look at how the Clear button triggers the ClearCanvas function:
<input type=”submit” value=”Clear Sketchpad” onclick=”clearCanvas(canvas,ctx);”>
You could make a button like this, but change the “clearCanvas..” part to make it call something like e.g. “setLinesize(5)” for a button for small line width, or “setLinesize(20)” for a larger width.
Your “setLinesize” function would then simply set the linesize variable:
function setLinesize(size) {
linesize=size;
}
Now the new value in linesize will be used by the drawDot or drawLine functions.
unable to do and I have to submit my work at college I have copied all your code and make chnages but i nedd buttons working to change lineWidth plz provide the code …..
Would it be possible to have a more complex custom image/shape as a brush?
Sure, you would need to first create the image object in Javascript (or get a reference to an image already loaded on the page), and then use the HTML5 “drawImage” function instead of the “beginPath, arc, closePath, fill” commands in drawDot().
Try searching for html5 drawImage for more info.
how can we add multiple color?
The drawDot function fixes the color at black at the moment with the following line:
r=0; g=0; b=0; a=255;
Instead of this line, another way of doing it is to pass the RGB color values in to the function parameters, like this:
function drawDot(ctx,x,y,size,r,g,b,a) {
Now anywhere that you call drawDot, you can pass different color values to it.
—
Alternatively, you can get the RGB value from somewhere else, like HTML elements on the page. There are a lot of different ways to do this, depending on if you’re using a color picker type widget, or just a drop-down box with color names like “red”,”blue” etc.
As a simple test, instead of the r=0; g=0; b=0; a=255; line, you can set it to use random colors by first adding this function:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max – min + 1)) + min;
}
Then use the following in the drawDot function:
r=getRandomInt(0,255);
g=getRandomInt(0,255);
b=getRandomInt(0,255);
a=255; // full opacity
If you wanted to take the color value from a dropdown box instead, you would first need to get the currently selected drop-down value by using something like
dropdown_value = document.getElementById(‘dropdownbox_id’);
..and then setting r, g and b based on what is contained in dropdown_value.
Please complete code with multiple color selection, pen size selection and open image!
Very thanks
Best regards
Just perfect!
Love this bit of code, seems to be more reliable on mobile devices when compared to some other Canvas drawing scripts I’ve come across before. Only issue I have is on mobile, the touchY points seems to be about 150px off. Any idea why these are being defined incorrectly, or where to look in the function to test?
Thanks for the comments 🙂
What device are you trying it on? Also, have you tried it in both portrait and landscape orientation?
It might be something to do with the scrolling position of the page in a smaller viewport.
You could try replacing the touch.pageX and touch.pageY in the code with
touch.screenX and touch.screenY or touch.clientX and touch.clientY to see if it helps.
very informative …
Hello!
This is a very nice article, I was able to create my own whiteboard thanks to you. I would like to know if there is a way to add more users to paint at the same time, is it possible to achieve this?
Thanks!
I really appreciate you putting together such a clear and comprehensive outline! However, after following your guideline, I now need to implement two separate drawing pads on the same webpage…!
How should I approach this?
Thanks a million for your help so far!
Love your work – thank you for sharing!
This was awesome. I appreciate the touch explanation. Massive help. I also integrated with Interact.js to add color pallete buttons.
Great to hear it Lance!
Hello, How can I save my scretchpad as image?
Thanks
Hi,
I’ve added a new section “I just want to save the Sketchpad as an image?” along with some demo code which allows you to save the image.
There are alternative ways of doing this depending on whether you need it to also work offline or not.
This is great! Thanks so much. I have a question though. I am using the drawLine version of the code, and when I make the canvas something bigger, like 1000×1000, now the lines are really delayed in drawing. Firstly, what is the reason for the laggyness of a larger canvas and secondly, is there anything I could do to remedy the issue? Thanks.
Glad you liked it 🙂
What device/browser are you testing it on?
I just tried a 1000×1000 canvas on a laptop using Chrome, and on an earlier iPad, and the performance is not bad (slightly faster on the laptop).
Older android devices may suffer a bit though.
I think the reason that larger canvases are slower is perhaps because the browser has to repaint the entire canvas each time a change is made, instead of just updating smaller areas.
You can also try commenting out the “ctx.strokeStyle” line in drawLine and move that out into a separate setStyle function if it appears to be slowing things down.
I am running Chrome on a Nexus 10. I realize now that I am connecting to a local server that is hosting the sketchpad.html for testing so that may have something to do with the delay, but I assumed that once the page was loaded, everything was being done locally. I did try commenting out the ctx.strokeStyle as you suggested but that didn’t affect the delay. Thanks for trying to help me troubleshoot this.
Sorry adding more information to try and help. So what I have tried right now is setting the #sketchpad size to 1000×1000 but the <canvas id="sketchpad" to 200×200. Now obviously this makes it so that I can only draw in a 200×200 area, but it is now drawing without any of the lag. I have tried different values for both the canvas and the #sketchpad to try and pin down which one is causing the issue. Switching the 2, ie canvas at 1000×1000 and #sketchpad at 200×200 still shows no delay.
I can’t seem to understand how to make connected lines instead of dots. I looked at the description below the source code and I couldn’t grasp how you made the drawLine function. Could you post updated source code on how to do that? Thanks!
I’ve added source code for drawing lines and a short explanation to the “Draw connected lines instead of dots” section towards the end.
How would you add a second canvas?
Hi,
I’ve updated the post with an example of adding a second canvas. Actually it should work for any number of canvasses, although you might need to test the performance.
There’s a short explanation and a working example with two sketchpads under the heading “I would like to have more than one Sketchpad on the screen”.