Creating a drawing board using Canvas in HTML 5

Sheonarayan
Posted by in HTML 5 category on for Advance level | Points: 250 | Views : 25989 red flag
Rating: 5 out of 5  
 3 vote(s)

In this article, I am going to explain how to design a drawing board or paint brush using Canvas in HTML5.

Introduction


After introduction of Canvas element in HTML 5, it has become easy to draw lines, shapes, images and perform animations in the browser without taking help of any third party component or plug-ins such as Flash, Silverlight etc. If you want to know more about Canvas, read this article by Sourav or this article from Mallesh.


Objective


The objective of this article is to explain how to create Paint brush type of application or a Drawing board using Canvas element of HTML 5 that works in the browser without any dependency on third party application or plug-ins. Our final product would look almost similar to below image.



Creating panel for Selected commands and Tool selection



This panel of our drawing board app shows selected color, selected line width of the brush. Also it provides option to choose the tool for the drawing.


The code to design this panel is below.
<div class="table">
        <div class="tr0">        
            <div class="td spacerH" style="width:100px;">Selected color: <div class="clrPicker" id="divSelectedColor" style="background-color:black;"></div></div>
            <div class="td" style="width:130px;">Selected Line width: <div class="lineWidthC"><div class="lineWidth" id="divLineWidth" style="height:5px;"></div></div></div>
            

            <div class="td">
                Select Drawing tool : <br />
                <input type="radio" name="dTool" id="dToolP" value="Pencil" checked="checked" /> <label for="dToolP"><img src="/images/pencil.gif" />Pencil</label>
                <input type="radio" name="dTool" id="dToolL" value="Line" /> <label for="dToolL"> <img src="/images/line.gif" />Line</label>
                <input type="radio" name="dTool" id="dToolR" value="Rectangle" /> <label for="dToolR"> <img src="/images/rectangle.gif" />Rectangle</label>
                <input type="radio" name="dTool" id="dToolC" value="Circle" /> <label for="dToolC"> <img src="/images/Circle.gif" />Circle</label>
            </div>
        </div>
    </div>

Creating the drawing panel and right side panel


Drawing panel for our app allows to draw a shape, art and right panel allows 
  1. to choose or set color
  2. select an Eraser to erase drawing
  3. select or set line width


The code to create our drawing panel, color, eraser and line width panel is below.
<div id="board" style="width: 930px;">

        <canvas id="kfCanvas" width="800px" height="500px;" style="border: 3px dotted #000;cursor:crosshair;">Sorry, your browser doesn't support canvas technology.
        </canvas>

        <div style="float: right;">

            <div>
            Color picker: 
            <div class="clrPicker" style="background-color:black;" onclick="SetBrushColor('black')"></div>
            <div class="clrPicker" style="background-color:red;" onclick="SetBrushColor('red')"></div>
            <div class="clrPicker" style="background-color:blue;" onclick="SetBrushColor('blue')"></div>
            <div class="clrPicker" style="background-color:green;" onclick="SetBrushColor('green')"></div>
            <div class="clrPicker" style="background-color:orange;" onclick="SetBrushColor('orange')"></div>
            <div class="clrPicker" style="background-color:yellow;" onclick="SetBrushColor('yellow')"></div>
                <div>Set color<br />
                    <input type="text" title="Write color code and click button to set new brush color" name="clrText" id="clrText" style="width:50px;" placeholder="Color code/name" />
                    <input type="button" value="Set" onclick="SetBrushColor('Text')" />
                </div>

            <div class="clrPicker" onclick="SetBrushColor('white')" style="width:45px;height:25px;padding-top:3px;">&nbsp;Eraser</div>

                </div>

            <div>
                Line width: 
                <div class="lineWidthC" onclick="SetLineWidth('5')"><div class="lineWidth" style="height:5px;"></div></div>
                <div class="lineWidthC" onclick="SetLineWidth('4')"><div class="lineWidth" style="height:4px;"></div></div>
                <div class="lineWidthC" onclick="SetLineWidth('2')"><div class="lineWidth" style="height:2px;"></div></div>
                <div>Set width<br />
                    <input type="text" title="Write line width and click button to set new line width" name="widthText" id="widthText" style="width:50px;" placeholder="Width in pixel" />
                    <input type="button" value="Set" onclick="SetLineWidth('0')" />
                </div>
            </div>

        </div>
    </div>
In the above code snippet, we have 6 color boxes with different different color, clicking those boxes calls SetBrushColor function. We also have a textbox and a Set button that is used to set custom color for the drawing.

The eraser box simply calls the same SetBrushColor method with 'white' parameter.

It also has three line width boxes, clicking those calls SetLineWidth function that sets the line width. In case we want to define the line width ourselves we can use the textbox and the set button.

To create a complete UI like the first image above, simply merge the first and second code snippets one after another.

CSS class used in this app

We are also using some .css classes in our code snippets and here are those.
<style type="text/css">
    .clrPicker
    {
        width: 30px;
        height: 30px;
        border: 1px solid #808080;
        margin-bottom: 5px;
        cursor:pointer;
    }
    .lineWidth
    {
        background-color:black;
        margin-bottom:5px;
        margin-top:15px;
    }
    .lineWidthC
    {
        width:30px;
        height:30px;
        border:1px dotted #808080;
        margin-bottom:5px;
        overflow:hidden;
        cursor:pointer;
    }
</style>

Above code simply create the UI without any functionalities as Canvas element has no properties or behaviours or commands to draw any kind of shapes or drawing. It always needs JavaScript to instruct what to do.

JavaScript code to make the Paint brush or Drawing board app live


Some global variables being used in this page are below.

var curColor = 'black'; //$('#selectColor option:selected').val(); // set color here
var lineWidth = 5;
var context;
var startX, startY;
var canvasX, canvasY;
var width, height;
var toolSelected;
  1. curColor is the default color selected
  2. lineWidth is the default line width selected
  3. context is the Canvas context
  4. startX is the starting point from left on the canvas (when mouse is clicked and drawing starts)
  5. startY is the starting point from top on the canvas (when mouse is clicked and drawing starts)
  6. canvasX is the ending point from left on the canvas (when mouse is clicked and drawing ends)
  7. canvasY is the ending point from top on the canvas (when mouse is clicked and drawing ends)
  8. width is the difference between canvasX and startX
  9. height is the difference between canvasY and startY
  10. toolSelected is the variable that holds the tool selected by the user from top - left of the drawing board
Now lets see the heart of the JavaScript code for this drawing board that makes this drawing board live.
var kfCanvas = document.getElementById("kfCanvas"); // jQuery doesn't work as .getContext throw error
if (kfCanvas) {
    var isDown = false;
    context = kfCanvas.getContext("2d");
    context.lineWidth = lineWidth; // set line width here

    DrawAWhiteBase(); // Draw a white base on the canvas

    $(kfCanvas).mousedown(function (e) {
        isDown = true;
        startX = e.pageX - kfCanvas.offsetLeft;
        startY = e.pageY - kfCanvas.offsetTop;
        toolSelected = $("input[type='radio'][name='dTool']:checked");

        if (toolSelected.length > 0) {
            toolSelected = toolSelected.val();
            // pencil
            if (toolSelected == 'Pencil') {

                context.lineWidth = lineWidth;
                context.strokeStyle = curColor;
                context.lineCap = 'round';
                context.beginPath();
                context.moveTo(startX, startY)
            }
        }
    }).mousemove(function (e) {
        if (isDown != false) {
            canvasX = e.pageX - kfCanvas.offsetLeft;
            canvasY = e.pageY - kfCanvas.offsetTop;
            width = Math.abs(canvasX - startX);
            height = Math.abs(canvasY - startY);

            var beginrad = startX;
            var endrad = canvasX;
            var radius = endrad - beginrad;  //to calculate circle radius

            var toolSelected = $("input[type='radio'][name='dTool']:checked");
            if (toolSelected.length > 0) {
                toolSelected = toolSelected.val();
                // pencil
                if (toolSelected == 'Pencil') {

                    context.lineTo(canvasX, canvasY);
                    context.stroke();
                }
                    // line
                else if (toolSelected == 'Line') {
                    DrawLine(startX, startY, canvasX, canvasY);
                }
                    // rectangle
                else if (toolSelected == 'Rectangle') {
                    DrawRect(startX, startY, width, height);
                }
                else if (toolSelected == 'Circle') {
                    DrawCircle(startX, startY, radius);
                }
            }


        }
    }).mouseup(function (e) {
        isDown = false;
        context.closePath();
    });
}

First we are holding the canvas element in the kfCanvas variable and if it exists, we are entering into the condition block. Then setting the context of the canvas that is used to draw shapes or an art, then lineWidth of the canvas to the lineWidth by default set in the global variable. 

To make the Canvas background white, we are calling DrawAWhiteBase() function (we will see it later on). We are doing it purposefully as saving this canvas as .png creates transparent background.

Now we are attaching mousedown, mousemove and mouseup event on the canvas element.

mousedown event

This sets the isDown flag to true, that let us know that user has pressed the mouse. Then it calculates the startX and startY value (mouse position from left and top where the mouse was clicked on the canvas).

It also sets the tools selected variable to know which tool is selected by the user currently.

If user has selected Pencil then it sets the lineWidth, strokeStyle, lineCap properties of the context and moving the cursor to the clicked position.

mousemove event

This checks if mouse is clicked and user is keep holding the mouse while moving the mouse and sets following
  • canvasX the current position of the mouse being dragged from left
  • canvasY the current position of the mouser being dragged from top
calculates the width and height of the selection. 

In case user has selected Circle tool, we need to know the radius too so we are also calculating that. Now based on tool selected we are performing some action or calling respecting functions

If tool selected is 
  • Pencil - we are drawing the line 
  • Line - calling DrawLine function by passing starting position of the mouse and ending position of the mouse
  • Rectangle - calling DrawRect function by passing starting position of the mouse, width and height
  • Circle - calling DrawCircle by passing starting position of the mouse and radius
mouseup event

In this event, we are setting the isDown flag and calling closePath function of the canvas to indicate that the drawing is complete.

Now, lets see each of the javascript functions we have used in the HTML UI code

DrawAWhileBase()

It is being called when page loads.
function DrawAWhiteBase() {
    context.beginPath();
    context.fillStyle = "white";
    context.fillRect(0, 0, kfCanvas.width, kfCanvas.height);
    context.closePath();
}
This function fills the entire width and height of the canvas with the white color, this comes as a base of the drawing board for us.

DrawLine()

This function is called when Line tool is selected and user drags the mouse on the canvas.
function DrawLine(fromX, fromY, toX, toY) {
    context.lineWidth = lineWidth;
    context.strokeStyle = curColor;
    context.beginPath();
    context.moveTo(fromX, fromY); //moveTo starts the line (x,y coords of the initial point)
    context.lineTo(toX, toY); //lineTo ends the line (x,y coords of the final point)
    context.stroke();
}
This function receives starting and ending position of the mouse drag draws a line.

DrawRect()

This function is called when Rectangle tool is selected and user drags the mouse on the canvas.
function DrawRect(mouseX, mouseY, width, height) {
    context.beginPath();
    context.rect(mouseX, mouseY, width, height);
    context.fillStyle = curColor;
    context.closePath();
    context.fill();
}
This function takes the starting position of the mouse draw, width and height and draws a rectangle.

DrawCircle()

This function is called when Circle tool is selected and user drags the mouse on the canvas.
function DrawCircle(x, y, r) {
    context.beginPath();
    context.fillStyle = curColor;
    context.arc(x, y, r, 0, Math.PI * 2, true);
    context.closePath();
    context.fill();
}

This function takes starting position of the mouse drag and radius and draw a circle.

SetBrushColor()

This function is called by onclick event of the color boxes at the right side or color set button at the right panel. It also gets called when Erase box is clicked.
function SetBrushColor(c) {
    if (c == 'Text') {
        c = $("#clrText").val();
    }
    curColor = c;
    $("#divSelectedColor").css('background-color', c);
}
This function performs more than one activities
  1. It checks whether this function is being called on click of the Set button, in that case it takes the value of the Color set TextBox and set to the c variable.
  2. Then it sets the value of curColor global variables that is being used through out the page for several drawing functions.
  3. Changes the top panel selected color box to the selected color.

SetLineWidth()

This function is called when Line width boxes are clicked or Set button of the Line width button is clicked from the right side panel.
function SetLineWidth(w) {
    if (w == '0') {
        w = $("#widthText").val();
    }
    lineWidth = w;
    context.lineWidth = w; // set line width here
    $("#divLineWidth").css('height', w + 'px');
}
This function also performs more than one activities
  1. If this function is being called on click of the line width Set button then it gets the textbox value and set to w.
  2. Then sets the lineWidth global variable so that all other drawing functions also use the same line width
  3. setting the context lineWidth
  4. setting the selected line width into selected line width box at the top panel

Saving the canvas as Image on the server


You must have noticed in the 1st image at the top we have a Save Drawing button at the top - left, that basically helps us to save the drawing done on the canvas to the server.

I have already explained this in another article of mine, please click here to read that article.

Thanks so much for reading this, do vote and share your comment or feedback about this article. If you liked this, please do share to the social websites, your friends and colleagues.

You can see the live demo of this drawing board, click below link

Live Demo (I am keep working on this project so the live demo might be more advanced than what is described in this article)


UPDATED:

To achieve Undo and Redo functionalities in the Drawing board


To achieve Undo and Redo functionality in this Drawing board, keep historySave function inside .mouseup event of the canvas.

.mouseup(function (e) {
        isDown = false;
        context.closePath();
        historySave();
    });
In the below code we are first declaring an array where we would keep the canvas snapshot till the last activity performed by the user.
// START - Undo Redo functionality
var undoRedo = new Array();
var unStep = -1;

function historySave() {
    unStep++;
    while (undoRedo.length > 20) {
        undoRedo.shift();
        unStep--;
    }
    if (unStep !== 0 && unStep < undoRedo.length) {
        undoRedo.length = unStep;
        unStep++;
    } else {
        undoRedo.length = unStep;
    }
    undoRedo.push(document.getElementById('kfCanvas').toDataURL());    
}

function Undo() {
    if (unStep > -1) {
        unStep--;
        var canvasPic = new Image();
        canvasPic.src = undoRedo[unStep];
        canvasPic.onload = function () { context.drawImage(canvasPic, 0, 0); }
    }
}

function Redo() {
    if (unStep < undoRedo.length - 1) {
        unStep++;
        var canvasPic = new Image();
        canvasPic.src = undoRedo[unStep];
        canvasPic.onload = function () { context.drawImage(canvasPic, 0, 0); }
    }
}

// END - Undo Redo functionality
In this case historySave function is keeping track of 20 canvas snapshots so that user will be able to go 20 steps backwards and forwards.

historySave()

This function simply pushes the current snapshot of the image to the array, remember that we are calling this function in .mouseup event so whenever an activity is complete, this function is called.

Undo()

This function go 1 step backward and get the last canvas snapshot and draw on the canvas.

Redo()

This function go 1 step forward if exists and get the canvas snapshot and raw on the canvas.


Reference


The idea of creating a drawing board using Canvas element and the base code was taken from http://www.onlywebpro.com/demo/gamedev/canvas_drawing_board.html. Thanks to onlywebpro for providing the strating point.
Page copy protected against web site content infringement by Copyscape

About the Author

Sheonarayan
Full Name: Sheo Narayan
Member Level: HonoraryPlatinum
Member Status: Administrator
Member Since: 7/8/2008 6:32:14 PM
Country: India
Regards, Sheo Narayan http://www.dotnetfunda.com
http://www.snarayan.com
Ex-Microsoft MVP, Author, Writer, Mentor & architecting applications since year 2001. Connect me on http://www.facebook.com/sheo.narayan | https://twitter.com/sheonarayan | http://www.linkedin.com/in/sheonarayan

Login to vote for this post.

Comments or Responses

Login to post response

Comment using Facebook(Author doesn't get notification)