Click here to Skip to main content
Click here to Skip to main content

Building a Signature Control Using Canvas

, 2 Aug 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
The article explains how to write a signature control using the canvas element and mouse events.

Introduction

Lately, I was developing a Windows 8 Metro app POC (Proof Of Concept) for a customer. One of the requirements in the POC was to capture customer’s signature which are written using touch events. In this article, I’ll show you how to write such a signature control using the HTML5 canvas element and mouse events. The same functionality can also be used in touch platforms using minor changes.

Canvas Element Basics

The canvas element is a drawing surface which can be placed inside a web page. It is part of the HTML5 specifications and is implemented in most of the major web browsers. The canvas exposes a set of JavaScript APIs that enables the developer to draw pixel based graphics. In order to use a canvas, you first need to create it inside your web page. Here is how you declare a canvas element:

<canvas id="myCanvas" width="300px" height="300px"></canvas>

Now that you have a canvas on the web page, you can use its JavaScript API to draw graphics. In order to draw, you will have to acquire a canvas context. Here is a code sample that shows how to get the context out of a canvas element:

var canvas = document.getElementById("signatureCanvas"),
    ctx = canvas.getContext("2d");   

In the code, two variables are declared – canvas and ctx. You get the canvas element (mostly by using its id property) and then use the getContext function to retrieve the drawing context. The getContext function gets a context id parameter that can have the 2d value or experimental-webgl value if webgl is supported in the browser. If you like to read more about creating 3d graphics using webgl, you can start from here

After you grab the drawing context, you can start drawing using canvas APIs. There are a lot of functions such as fillRect (to draw a rectangle with a fill color) and clearRect (to clear a rectangle area in the canvas). Since this article deals mostly with creating a control which wrap a canvas, I encourage you to read more about the canvas in the following articles before you continue:

Creating the Control Elements on The Fly

Now that you know a little bit about the canvas, lets start developing the signature control. At first, you will want to create the HTML look and feel. In order to do that, you can use the document.createElement function to create elements on the fly and build the representation of the control. Here is the code I used in the suggested solution:

function createControlElements() {            
    var signatureArea = document.createElement("div"),
        labelDiv = document.createElement("div"),
        canvasDiv = document.createElement("div"),
        canvasElement = document.createElement("canvas"),
        buttonsContainer = document.createElement("div"),
        buttonClear = document.createElement("button"),
        buttonAccept = document.createElement("button");
 
    labelDiv.className = "signatureLabel";
    labelDiv.textContent = label;
 
    canvasElement.id = "signatureCanvas";
    canvasElement.clientWidth = cWidth;
    canvasElement.clientHeight = cHeight;
    canvasElement.style.border = "solid 2px black";
 
    buttonClear.id = "btnClear";
    buttonClear.textContent = "Clear";
 
    buttonAccept.id = "btnAccept";
    buttonAccept.textContent = "Accept";
 
    canvasDiv.appendChild(canvasElement);
    buttonsContainer.appendChild(buttonClear);
    buttonsContainer.appendChild(buttonAccept);
 
    signatureArea.className = "signatureArea";
    signatureArea.appendChild(labelDiv);
    signatureArea.appendChild(canvasDiv);
    signatureArea.appendChild(buttonsContainer);
 
    document.getElementById(containerId).appendChild(signatureArea);
}

As you can see, I create some in-memory elements and then set some attributes on them. After that, I append the created elements to each other to create the HTML fragment and wire the fragment to a container element with containerId.

Implementing Drawing in The Canvas

Now that you have the elements in hand, the next task will be to implement the drawing in the canvas. In order to do that, you will need to add mouse event listeners to the canvas. The most appropriate events are mousedown and mouseup. Here is the code to wire the events:

canvas.addEventListener("mousedown", pointerDown, false);
canvas.addEventListener("mouseup", pointerUp, false);

and here is the code of pointerDown, pointerUp and paint functions:

function pointerDown(evt) {
    ctx.beginPath();
    ctx.moveTo(evt.offsetX, evt.offsetY);
    canvas.addEventListener("mousemove", paint, false);
}
 
function pointerUp(evt) {
    canvas.removeEventListener("mousemove", paint);
    paint(evt);
}
 
function paint(evt) {
    ctx.lineTo(evt.offsetX, evt.offsetY);
    ctx.stroke();
}

In the pointerDown function, you use the beginPath function to start a drawing path. Then, the context is moved to the point that the mouse point using the event’s offsetX and offsetY properties. After that, you wire an event listener to the mousemove event. In the paint function that is invoked while the mouse is being moved, you move the context to the new point and then use the stroke function to draw the line between the previous point and the current point. When the mouse button is released, the pointerUp function is called. In the pointerUp function, you draw the last line to the end point and remove the event listener to the mousemove event listener. Removing the mousemove event listener will prevent the continuation of the drawing when you hover on the canvas element.

Getting the Signature Image Data as Byte Array

Once the signature is drawn on the canvas, you will probably want to extract it. This can be done using the context’s getImageData which returns the data drawn in the canvas. The return type of the function call has a data property which holds a byte array representing the canvas’ pixels. The following function can help to retrieve the signature:

function getSignatureImage() {
    return ctx.getImageData(0, 0, canvas.width, canvas.height).data;
}

The Whole Control Implementation

Lets wrap all the previous functions into a JavaScript control. Here is the control’s implementation:

(function (ns) {
    "use strict";
 
    ns.SignatureControl = function (options) {
        var containerId = options && options.canvasId || "container",
            callback = options && options.callback || {},
            label = options && options.label || "Signature",
            cWidth = options && options.width || "300px",
            cHeight = options && options.height || "300px",
            btnClearId,
            btnAcceptId,
            canvas,
            ctx;
 
        function initCotnrol() {
            createControlElements();
            wireButtonEvents();
            canvas = document.getElementById("signatureCanvas");
            canvas.addEventListener("mousedown", pointerDown, false);
            canvas.addEventListener("mouseup", pointerUp, false);
            ctx = canvas.getContext("2d");            
        }
 
        function createControlElements() {            
            var signatureArea = document.createElement("div"),
                labelDiv = document.createElement("div"),
                canvasDiv = document.createElement("div"),
                canvasElement = document.createElement("canvas"),
                buttonsContainer = document.createElement("div"),
                buttonClear = document.createElement("button"),
                buttonAccept = document.createElement("button");
 
            labelDiv.className = "signatureLabel";
            labelDiv.textContent = label;
 
            canvasElement.id = "signatureCanvas";
            canvasElement.clientWidth = cWidth;
            canvasElement.clientHeight = cHeight;
            canvasElement.style.border = "solid 2px black";
 
            buttonClear.id = "btnClear";
            buttonClear.textContent = "Clear";
 
            buttonAccept.id = "btnAccept";
            buttonAccept.textContent = "Accept";
 
            canvasDiv.appendChild(canvasElement);
            buttonsContainer.appendChild(buttonClear);
            buttonsContainer.appendChild(buttonAccept);
 
            signatureArea.className = "signatureArea";
            signatureArea.appendChild(labelDiv);
            signatureArea.appendChild(canvasDiv);
            signatureArea.appendChild(buttonsContainer);
 
            document.getElementById(containerId).appendChild(signatureArea);
        }
 
        function pointerDown(evt) {
            ctx.beginPath();
            ctx.moveTo(evt.offsetX, evt.offsetY);
            canvas.addEventListener("mousemove", paint, false);
        }
 
        function pointerUp(evt) {
            canvas.removeEventListener("mousemove", paint);
            paint(evt);
        }
 
        function paint(evt) {
            ctx.lineTo(evt.offsetX, evt.offsetY);
            ctx.stroke();
        }
 
        function wireButtonEvents() {
            var btnClear = document.getElementById("btnClear"),
                btnAccept = document.getElementById("btnAccept");
            btnClear.addEventListener("click", function () {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
            }, false);
            btnAccept.addEventListener("click", function () {
                callback();
            }, false);
        }
 
        function getSignatureImage() {
            return ctx.getImageData(0, 0, canvas.width, canvas.height).data;
        }
 
        return {
            init: initCotnrol,
            getSignatureImage: getSignatureImage
        };
    }
})(this.ns = this.ns || {});

First, you create a scope for the control using a JavaScript namespace. In the namespace, you declare a constructor function for the SignatureControl. The control can get a list of options which can help to configure it’s appearance and behavior. For example, the callback option is called when you click on the accept button. The control will expose two functions – init and getSignatureImage. The init function will be responsible to initialize all the elements, to wire events listeners to the control’s buttons and to wire the event listeners to the mouse events. The getSignatureImage function will be responsible to retrieve the signature byte array.

Using the Control in a HTML Page

After you have the control, lets see how to use it inside a web page. The following web page shows how to use the control:

<!doctype html>
<html>
<head>
    <title>Signature</title>
    <link href="signature.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="signature.js"></script>  
    <script type="text/javascript">
        function loaded() {
            var signature = new ns.SignatureControl({ containerId: 'container', callback: function () {
                    alert('hello');
                } 
            });
            signature.init();
        }
 
        window.addEventListener('DOMContentLoaded', loaded, false);
    </script>  
</head>
<body>
    <div id="container">        
    </div>
</body>
</html>

When the DOM content finish loading, you create a signature object using its constructor function and some options. Then, all you have to do is call the init function to create the control and enable its functionality. If you want to retrieve the signature you can use the following code:

var signatureByteArray = signature.getSignatureImage();

Here is a screenshot of the control in action:

The Signature Control

Summary

This article showed you how to create a control to capture signatures. In order to use the same functionality with Windows 8 touch events, all you have to do is to replace the call for mouse events with their corresponding touch events (for example mouseup will turn into MSPointerUp). 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gil Fink
Technical Lead sparXys
Israel Israel
Gil Fink is a web development expert and ASP.Net/IIS Microsoft MVP. He is the founder and owner of sparXys. He is currently consulting for various enterprises and companies, where he helps to develop Web and RIA-based solutions. He conducts lectures and workshops for individuals and enterprises who want to specialize in infrastructure and web development. He is also co-author of several Microsoft Official Courses (MOCs) and training kits, co-author of "Pro Single Page Application Development" book (Apress) and the founder of Front-End.IL Meetup. You can read his publications at his website: http://www.gilfink.net
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionPlease advise on how to save to sql database PinmemberUriel Tinashe Patsanza9-Jun-14 22:46 
AnswerRe: Please advise on how to save to sql database PinmemberGil Fink12-Aug-14 2:08 
Questionreading byte array Pinmemberraghum1126-Mar-13 1:16 
AnswerRe: reading byte array PinmemberGil Fink30-Mar-13 1:45 
QuestionThe asp .net application created from this code is not working in Ipad Safari Pinmemberrahulkasar8-Jan-13 6:38 
AnswerRe: The asp .net application created from this code is not working in Ipad Safari PinmemberMember 888372716-Feb-13 6:01 
AnswerRe: The asp .net application created from this code is not working in Ipad Safari PinmemberMiller Nguyen2-Nov-14 15:33 
GeneralRe: The asp .net application created from this code is not working in Ipad Safari PinmemberPallavi.varade16hrs 42mins ago 
QuestionNice control!! PinmemberAkshay Srinivasan215-Aug-12 12:19 
GeneralMy vote of 5 PinmemberSasha Goldshtein1-Aug-12 5:40 
GeneralRe: My vote of 5 PinmemberGil Fink1-Aug-12 20:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 2 Aug 2012
Article Copyright 2012 by Gil Fink
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid