Creating a Simple Paint App with HTML5 Canvas and Javascript

modified

Introduction

Although HTML5’s formal specification is still under development, the technology is already growing in popularity. HTML5, as supported by many popular web browsers, already brings together a set of client-side technologies for producing highly interactive and responsive applications. One of the more talked-about parts of the HTML5 toolkit is the Canvas element. This new element provides a graphical drawing surface, allowing developers to create graphics and animations, similar to other client-side technologies such as Flash. The canvas element is quite easy to use, providing a quick and powerful starting point for creating a variety of client-side graphical applications.

In this tutorial, we’ll walk through creating a simple paint program in the web browser, with HTML5 and Javascript. We’ll create a toolbar, containing a set of paint colors and image stamps, and allow the user to paint within the canvas element and even save the image.

HTML5 Canvas Paint Application

First, Try the Example

Before getting started with the details behind implementing the HTML5 canvas paint application, let’s take a look at the actual paint app running in the web browser.

HTML5 Easy Paint

Starting with a Web Page

As with any HTML5 application, you can start out with a simple web page, containing the canvas element. The canvas element is actually an HTML5 tag, located within the body of the web page. Since we’ll be using Javascript to reference the canvas element and toolbar buttons, we’ll also include a link to the jQuery library. The HTML source code for a simple page can appear, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!DOCTYPE html>
<html>
<head>
<title>Paint</title>
<style type="text/css"><!--
#container { position: relative; }
#imageView { border: 1px solid #000; }
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
--></style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js">
</script>
<script type="text/javascript" src="paint.js"></script>
<script type="text/javascript" src="init.js"></script>
</head>
<body>
<div id="container" style="padding:5px 0px 0px 0px;">
<div id="colorToolbar" style="border: 1px solid black; float: left;">
<div id="red" style="background:red; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="pink" style="background:pink; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="fuchsia" style="background:fuchsia; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="orange" style="background:orange; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="yellow" style="background:yellow; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="lime" style="background:lime; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="green" style="background:green; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="blue" style="background:blue; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="purple" style="background:purple; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<div id="black" style="background:black; width:50px; height:50px;
float:left; border: 1px dashed white;"></div>
<div style="clear: both;"></div>
<div id="white" style="background:white; width:50px; height:50px; float:left;"></div>
<div style="clear: both;"></div>
<hr/>
<div id="fill" style="width:50px; height:50px; float:left;">
<img src="fill.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="cat" style="width:50px; height:50px; float:left;">
<img id="catImg" src="cat.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="dog" style="width:50px; height:50px; float:left;">
<img id="dogImg" src="dog.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="dragonfly" style="width:50px; height:50px; float:left;">
<img id="dragonFlyImg" src="fly.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="ladybug" style="width:50px; height:50px; float:left;">
<img id="ladyBugImg" src="bug.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="heart" style="width:50px; height:50px; float:left;">
<img id="heartImg" src="heart.png" width="50" height="50" /></div>
<div style="clear: both;"></div>
<div id="save" style="width:50px; height:50px; float:left;">Save</div>
<div style="clear: both;"></div>
</div>
<div id="canvasDiv" style="float: left;">
<canvas id="imageView">
<p>Unfortunately, your browser is currently unsupported by our web
application. We are sorry for the inconvenience. Please use one of the
supported browsers listed below, or draw the image you want using an
offline tool.</p>
<p>Supported browsers: <a href="http://www.opera.com">Opera</a>, <a
href="http://www.mozilla.com">Firefox</a>, <a
href="http://www.apple.com/safari">Safari</a>, and <a
href="http://www.konqueror.org">Konqueror</a>.</p>
</canvas>
</div>
<div id="stats" style="font-size:8pt; padding-left: 50px; float: left;">0 0</div>
</div>
</body>
</html>

Notice in the above HTML page, we’ve included a Javascript link to the jQuery library from Google’s CDN (content distribution network), which saves us from having to host the file ourselves. We’ve also included a link to our own Javascript files paint.js, and init.js. We’ll get to those in just a bit.

Notice the above page contains a “canvas” tag element within the page. This will serve as our drawing surface. All paint commands will be issued against the canvas element. We’ve also defined a couple of div objects, to serve as buttons for our toolbar. We simply color the div background and set a border, to simulate the appearance of a button. Once clicked, we’ll change the appearance of the div border, to simulate focus on the button.

With the HTML defined, we can start our initial programmatic hook to gain access to the canvas. This occurs within our init.js file.

Hooking and Initializing HTML5

We now have a web page with an HTML5 canvas element. The next step is to perform our logic upon loading the page. There are a variety of methods for doing this, such as overriding the “onload” event within the body tag. We can also simply include our init.js file and have it automatically add an event listener to the window, as follows:

1
2
3
if (window.addEventListener) {
window.addEventListener('load', function() { init(); });
}

The above code inserts a window event listener for the “load” event. The load event will then call our init() method after the page has loaded. The init() method is where the core of our HTML5 canvas initialization will occur.

Initializing the Canvas

When working with the HTML5 canvas, you’ll typically need to reference the canvas’s context object, which is where the drawing actually occurs. We can access the context by first obtaining a reference to the canvas element within the page, and then getting a reference to the “2d” context of the canvas. This occurs within the first 2 lines of our init() method, as shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var started = false;
var canvas, context;
var stampId = '';
var lastColor = 'black';
var lastStampId = '';

function init() {
canvas = $('#imageView').get(0);
context = canvas.getContext('2d');

// Auto-adjust canvas size to fit window.
canvas.width = window.innerWidth - 75;
canvas.height = window.innerHeight - 75;

canvas.addEventListener('mousemove', onMouseMove, false);
canvas.addEventListener('click', onClick, false);

// Add events for toolbar buttons.
$('#red').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#pink').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#fuchsia').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#orange').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#yellow').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#lime').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#green').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#blue').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#purple').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#black').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#white').get(0).addEventListener('click', function(e) {
onColorClick(e.target.id); }, false);
$('#cat').get(0).addEventListener('click', function(e) {
onStamp(e.target.id); }, false);
$('#dragonfly').get(0).addEventListener('click', function(e) {
onStamp(e.target.id); }, false);
$('#ladybug').get(0).addEventListener('click', function(e) {
onStamp(e.target.id); }, false);
$('#heart').get(0).addEventListener('click', function(e) {
onStamp(e.target.id); }, false);
$('#dog').get(0).addEventListener('click', function(e) {
onStamp(e.target.id); }, false);
$('#fill').get(0).addEventListener('click', function(e) { onFill(); }, false);
$('#save').get(0).addEventListener('click', function(e) { onSave(); }, false);
}

Note in the above code, we’ll also automatically adjust the width and height of the canvas to size to the window. This allows us to maximize the paint screen according to the window size. We also add a set of event listeners. The first listener is to handle the “mousemove” event. This will allow us to paint as the mouse is moved across the screen. We’ll also monitor the “click” event on the canvas, for stamping images on the screen when the mouse is clicked (if a stamp was selected by the user). Finally, we’ll add a set of listeners for each div button, to handle clicking the div and changing the color accordingly.

It’s Alive!

To get the basics of our paint program going, we’ll first handle the “mousemove” event by defining our onMouseMove() method, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function onMouseMove(ev) {
var x, y;

// Get the mouse position.
if (ev.layerX >= 0) {
// Firefox
x = ev.layerX - 50;
y = ev.layerY - 5;
}
else if (ev.offsetX >= 0) {
// Opera
x = ev.offsetX - 50;
y = ev.offsetY - 5;
}

if (!started) {
started = true;

context.beginPath();
context.moveTo(x, y);
}
else {
context.lineTo(x, y);
context.stroke();
}

$('#stats').text(x + ', ' + y);
}

The above code accesses the coordinates of the mouse in order to retrieve an x,y coordinate. We’ll then draw a line from the canvas’s current draw point to the mouse’s coordinate. As the mouse moves across the screen, this method will fire, drawing pixels along the way.

Note, the above code will continuously paint as the mouse moves, regardless if the user releases the mouse button. To only paint while the mouse button is clicked, you can toggle the value of “started” and handle it within the “click” event of the canvas.

Clicking the Canvas

As mentioned above about continuous paint, you can toggle the “started” variable in the click event handler. To keep our example simple, we’ll just include logic for displaying the image stamps upon clicking the canvas. You can certainly update the code to include painting on or off depending on holding down the mouse button as well. We’ll define our click event handler, as follows.

1
2
3
4
5
function onClick(e) {
if (stampId.length > 0) {
context.drawImage($(stampId).get(0), e.pageX - 90, e.pageY - 60, 80, 80);
}
}

The above code simply checks if we’ve already set a stampId (which is set when the user clicks one of the stamp buttons), and if so, draws the image on the canvas by using the drawImage() method, built into HTML5.

If you notice in the initial HTML for the page, the image stamp div buttons include an IMG image tag. This displays the image on the button. It also allows us to copy the image to draw onto the canvas with the drawImage() method. In the above code sample, the first parameter to the drawImage() method can take a reference to an IMG tag. It will use the contents of the IMG tag to draw onto the canvas surface.

An example of the div tag, containing an image element, appears as follows from our HTML page:

1
2
<div id="cat" style="width:50px; height:50px; float:left;">
<img id="catImg" src="cat.png" width="50" height="50" /></div>

Changing the Colors

Since we’ve also added listeners for handling the click events on the toolbar buttons, we can implement the onColorClick() method as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function onColorClick(color) {
// Start a new path to begin drawing in a new color.
context.closePath();
context.beginPath();

// Select the new color.
context.strokeStyle = color;

// Highlight selected color.
var borderColor = 'white';
if (color == 'white' || color == 'yellow') {
borderColor = 'black';
}

$('#' + lastColor).css("border", "0px dashed white");
$('#' + color).css("border", "1px dashed " + borderColor);

// Store color so we can un-highlight it next time around.
lastColor = color;
}

The above code first closes and re-opens the canvas’s context. This allows us to select a new color for painting on the HTML5 canvas. We choose a new color with the context.strokeStyle attribute. We’ll also set a border around the toolbar div button, to highlight that the color is selected. Note, we also keep a copy of the last color selected, so we can unhighlight the button’s border when a new color is clicked.

Flood Filling the Canvas Background

We can fill the background of the HTML5 canvas with a color in the onFill() method that we’ve added a listener for. The code simply involves calling the context.fillStyle to select a color and then calling context.fillRect() to paint a rectangle with the width and height of the HTML5 canvas block.

1
2
3
4
5
6
7
8
function onFill() {
// Start a new path to begin drawing in a new color.
context.closePath();
context.beginPath();

context.fillStyle = context.strokeStyle;
context.fillRect(0, 0, canvas.width, canvas.height);
}

Selecting an Image Stamp is Easy

We’ve already defined the actual painting of the image stamps when the user clicks the canvas. We now need to define the method for the button click event from the stamp toolbar button. Upon clicking the toolbar button for an image stamp, we’ll simply save the div id (which is passed to us from the original event listener hook) in the init() method. Later on, when the user clicks the canvas, we’ll paint the element associated with this Id, which happens to be the inner IMG image tag.

1
2
3
4
5
6
7
8
9
10
function onStamp(id) {
// Update the stamp image.
stampId = '#' + id;

$(lastStampId).css("border", "0px dashed white");
$(stampId).css("border", "1px dashed black");

// Store stamp so we can un-highlight it next time around.
lastStampId = stampId;
}

Saving a Canvas Image

After painting on the HTML5 canvas, the user can save the image as an actual picture by using the HTML5 toDataURL() method. For our example, we’ll save the image as a PNG and write it to the page. The user can then right-click the image and save it to disk. The code appears, as follows:

1
2
3
4
function onSave() {
var img = canvas.toDataURL("image/png");
document.write('<img src="' + img + '"/>');
}

Drawing Only When the Mouse is Down

This example has auto-draw enabled, meaning the application will continuously paint while the mouse is moved around the screen. This makes it easy for children to draw by just moving the mouse, without having to click. However, it’s easy to enhance the program to only paint while holding down the mouse button (as in most traditional paint programs) by making the following changes to paint.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Add this to the top. 
var enableDraw = false;

// Modify the init method.
function init() {
....

// Add these two lines.
canvas.addEventListener('mousedown', function(e) { enableDraw = true; }, false);
canvas.addEventListener('mouseup', function(e) { enableDraw = false; started = false; }, false);

....
}


// Then modify onMouseMove():
if (enableDraw) {
if (!started) {
started = true;

context.beginPath();
context.moveTo(x, y);
}
else {
context.lineTo(x, y);
context.stroke();
}
}

Conclusion

HTML5 provides an interesting and powerful set of technologies for creating the next generation of client and web-based applications. With the HTML5 canvas element, software developers can create a large variety of graphical applications, including charts, animations, games, and more. Although HTML5 is still under specification, many modern web browsers already include full support, allowing developers to implement fully functioning HTML5 applications to enhance productivity and user interaction.

See It In Action

Download @ GitHub

The source code for this example is available online in our GitHub repository.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share