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:
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).