A mobile-friendly "e-signature" made using the HTML5 canvas

The HTML5 canvas element has been around for a while now, and it’s great for lots of things: drawing, games, user input, and more. It’s also fairly easy to use, and its API is similar to other drawing APIs out there. What’s not so easy is getting the canvas to work with both mouse and touch events, a requirement for mobile-friendly applications.

Partially as a joke I wanted to add an “e-signature” feature to a friend’s project. Since it was meant to be mobile-friendly, it was time to use the canvas with touch support! Here’s how it was done:

You can check out the final demo here and view the code here.

Canvas Setup

Canvas setup is easy enough:

<canvas id="sig-canvas" width="320" height="160">
Get a better browser, bro.
</canvas>
// Set up the canvas
var canvas = document.getElementById("sig-canvas");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#222222";
ctx.lineWith = 2;

Mouse Input

In order to draw a signature, I needed to capture user input on the canvas. Starting with mouse input, I handled these three mouse events:

  1. mousedown - to toggle drawing mode “on”
  2. mouseup - to toggle drawing mode “off”
  3. mousemove - to toggle mouse position, used in drawing
// Set up mouse events for drawing
var drawing = false;
var mousePos = { x:0, y:0 };
var lastPos = mousePos;
canvas.addEventListener("mousedown", function (e) {
        drawing = true;
  lastPos = getMousePos(canvas, e);
}, false);
canvas.addEventListener("mouseup", function (e) {
  drawing = false;
}, false);
canvas.addEventListener("mousemove", function (e) {
  mousePos = getMousePos(canvas, e);
}, false);

// Get the position of the mouse relative to the canvas
function getMousePos(canvasDom, mouseEvent) {
  var rect = canvasDom.getBoundingClientRect();
  return {
    x: mouseEvent.clientX - rect.left,
    y: mouseEvent.clientY - rect.top
  };
}

Drawing

Now that I knew the state of the mouse, I could start drawing to the canvas. To make it happen smoothly and efficiently, I took advantage of the browser method requestAnimationFrame. I used a handy function that determines the appropriate method of getting the window’s animation frame or, failing that, falling back on a simple timeout loop:

// Get a regular interval for drawing to the screen
window.requestAnimFrame = (function (callback) {
        return window.requestAnimationFrame || 
           window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame ||
           window.oRequestAnimationFrame ||
           window.msRequestAnimaitonFrame ||
           function (callback) {
        window.setTimeout(callback, 1000/60);
           };
})();

To actually do the drawing, I set up a draw loop. I created a renderCanvas() function for drawing the signature, connecting the previous and current mouse positions with a line (if drawing is enabled). By hooking into the window’s animation frame and running that function in a loop, I got a fully interactive signature field!

// Draw to the canvas
function renderCanvas() {
  if (drawing) {
    ctx.moveTo(lastPos.x, lastPos.y);
    ctx.lineTo(mousePos.x, mousePos.y);
    ctx.stroke();
    lastPos = mousePos;
  }
}

// Allow for animation
(function drawLoop () {
  requestAnimFrame(drawLoop);
  renderCanvas();
})();

Touch Input

Since the project to which I was supposedly contributing was a modern web app, I needed to support smartphones and tablets. This meant adding touch controls to supplement the mouse controls. Keeping it simple, I only used one touch at a time (sorry, multitouch). For starters, I utilized three touch event counterparts to the mouse events from earlier:

  1. touchstart – to toggle drawing mode “on”
  2. touchend – to toggle drawing mode “off”
  3. touchmove – to track finger position, used in drawing

Because I wanted to play around with event dispatching, I used these touch events to trigger their mouse event counterparts and do the appropriate conversions (touch position to mouse position, etc).

// Set up touch events for mobile, etc
canvas.addEventListener("touchstart", function (e) {
        mousePos = getTouchPos(canvas, e);
  var touch = e.touches[0];
  var mouseEvent = new MouseEvent("mousedown", {
    clientX: touch.clientX,
    clientY: touch.clientY
  });
  canvas.dispatchEvent(mouseEvent);
}, false);
canvas.addEventListener("touchend", function (e) {
  var mouseEvent = new MouseEvent("mouseup", {});
  canvas.dispatchEvent(mouseEvent);
}, false);
canvas.addEventListener("touchmove", function (e) {
  var touch = e.touches[0];
  var mouseEvent = new MouseEvent("mousemove", {
    clientX: touch.clientX,
    clientY: touch.clientY
  });
  canvas.dispatchEvent(mouseEvent);
}, false);

// Get the position of a touch relative to the canvas
function getTouchPos(canvasDom, touchEvent) {
  var rect = canvasDom.getBoundingClientRect();
  return {
    x: touchEvent.touches[0].clientX - rect.left,
    y: touchEvent.touches[0].clientY - rect.top
  };
}

Unfortunately, all was not well. An issue arose from a conflict with built-in browser gestures. When I moved my finger on the canvas, I wanted the page to stay still so I could draw. However, if my page had horizontal or vertical scrolling (and it did) the page would move along with my finger and making proper drawing impossible. After some frustration, I stumbled upon the solution: preventing scrolling on document.body if the target of a touch event is the canvas.

// Prevent scrolling when touching the canvas
document.body.addEventListener("touchstart", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, false);
document.body.addEventListener("touchend", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, false);
document.body.addEventListener("touchmove", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, false);

And viola, a mobile-friendly e-signature!

Bonus: Save the Canvas as a Data URL

Now that I had a signature (or doodle, or whatever) it would make sense to save it somewhere. The cheap and easy solution is to save the contents of the canvas directly as a 64-bit data URL, and here’s how:

var dataUrl = canvas.toDataURL();

You can then easily store the data URL in a database, set it to the src attribute of an image element, etc.

Bonus: Clearing the Canvas

I don’t exactly know why (though I would love to know), but using canvas.clearRect() or canvas.fillRect() to clear or cover the canvas in white, respectively, didn’t actually clear the canvas. The next time I went to draw, the signature I thought I deleted would come back! The unintuitive solution is to reset the canvas’ width, which completely resets the canvas and its context.

function clearCanvas() {
    canvas.width = canvas.width;
}

Next Step: Scalable Canvas

The one thing my e-signature demo doesn’t do is scale the canvas based on the window size. As far as I know, the canvas needs to have a fixed height and width, which rules out media queries and CSS. I could hook into the window.resize event and do it through JavaScript, but that didn’t seem like a great solution. There was also the question: do I want scaling to cause a variety of image sizes since the canvas size will change, or am I just lazy and don’t want to do it? If you have any advice (aside from “don’t be lazy”), please let me know!

Resources: