Learn how to create a signature pad using HTML, CSS, and JavaScript. Get source code and step-by-step guidance for a seamless digital signature experience.
Table of Contents
Digital signatures are crucial in various web applications today, from e-commerce to document management. In this tutorial, we'll guide you through creating a signature pad using HTML, CSS, and JavaScript. You'll have access to source code and step-by-step instructions to enhance user experiences and streamline your web development projects.
Let's start making an amazing signature pad using HTML, CSS, and JavaScript step by step.
Join My Telegram Channel to Download the Project Source Code: Click Here
Prerequisites:
Before starting this tutorial, you should have a basic understanding of HTML, CSS, and JavaScript. Additionally, you will need a code editor such as Visual Studio Code or Sublime Text to write and save your code.
Source Code
Step 1 (HTML Code):
To begin, set up the basic HTML structure for your signature pad. Create a canvas element and other necessary HTML components.
Here's an explanation of each part of the code:
1. <!DOCTYPE html>: This declaration tells the browser that the document is written in HTML5.
2. <html lang="en">: The <html> element is the root element of the HTML document. lang="en" specifies that the document is in English.
3. <head>: The <head> section contains metadata about the document, such as character encoding, title, and external resources.
- <meta charset="UTF-8">: Specifies the character encoding of the document as UTF-8, which is a character encoding that supports a wide range of characters.
- <meta http-equiv="X-UA-Compatible" content="IE=edge">: This meta tag is often used for Internet Explorer compatibility settings. It instructs IE to use its latest rendering engine.
- <meta name="viewport" content="width=device-width, initial-scale=1.0">: This meta tag sets the viewport properties, ensuring that the webpage is displayed appropriately on various devices and screens.
- <title>Signature Pad</title>: Sets the title of the webpage to "Signature Pad." This title is displayed in the browser's tab or window.
- <link rel="stylesheet" href="styles.css">: This line links an external stylesheet (styles.css) to the HTML document. It's used to apply styles to the content on the page.
4. <body>: The <body> element contains the actual content of the webpage.
5. <section class="signature-component">: A <section> element with the class "signature-component." This is a container for the signature pad and related content.
- <h1>Draw Signature</h1>: A top-level heading displaying "Draw Signature."
- <h2>with mouse or touch</h2>: A subheading indicating that you can use a mouse or touch input to draw the signature.
- <canvas id="signature-pad" width="400" height="200"></canvas>: A <canvas> element with the ID "signature-pad" and dimensions of 400 pixels in width and 200 pixels in height. This is where the signature can be drawn using JavaScript.
6. <div>: A container for buttons.
- <button id="save">Save</button>: A button with the ID "save" that allows the user to save the signature.
- <button id="clear">Clear</button>: A button with the ID "clear" to clear the signature.
- <button id="showPointsToggle">Show points?</button>: A button with the ID "showPointsToggle" that toggles the display of points (for debugging or illustrative purposes).
7. External Scripts:
- <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>: Loads the Underscore.js library, a utility library for JavaScript.
- <script src="script.js"></script>: Loads a custom JavaScript file named "script.js." This script is responsible for handling signature drawings and interactions on the page.
Step 2 (CSS Code):
Style your signature pad to make it visually appealing and user-friendly. Utilize CSS to define the appearance and layout.
Here's an explanation of each part of the code:
1. *, :before, :after: These are CSS selectors. * selects all elements on the web page, while :before and :after select pseudo-elements that can be used to insert content before and after an element. In this code, they are setting the box-sizing property to border-box for all elements, including pseudo-elements. This means that the total width and height of an element will include its padding and border, not just its content.
2. html: This selector targets the HTML element. It sets the font property to 18px with the font family "Helvetica Neue" or a sans-serif font. This will set the default font and size for text within the entire HTML document.
3. body: This selector targets the body element of the HTML page. It sets the text-align property to center, which means that the text content inside the <body> element will be horizontally centered.
4. .signature-component: This is a class selector, which is used to target elements with the class "signature-component." It sets several CSS properties for elements with this class:
- text-align: left: Text content inside elements with this class will be left-aligned.
- display: inline-block: Elements with this class will be displayed as inline-block elements. This allows them to flow inline with other elements but still be styled and sized like block elements.
- max-width: 100%: The maximum width of elements with this class is set to 100% of their containing element.
5. .signature-component h1: This targets <h1> elements that are descendants of elements with the class "signature-component." It sets the margin-bottom property to 0, removing any bottom margin for <h1> elements within these components.
6. .signature-component h2: This targets <h2> elements within the "signature-component" class. It sets the margin property to 0, removing margins on all sides, and the font-size to 100%, which essentially sets the font size to the browser's default size.
7. .signature-component button: This targets <button> elements within the "signature-component" class. It sets various properties for these buttons, including padding, background, box-shadow, margin-top, border, and font-size. This styles buttons within these components.
8. .signature-component button.toggle: This targets buttons with the class "toggle" within the "signature-component" class. It sets the background property to create a translucent red background color when the button has the "toggle" class.
9. .signature-component canvas: This targets <canvas> elements within the "signature-component" class. It sets the display property to block, positioning to relative, and adds a 1px solid border. This styles canvas elements within these components.
10. .signature-component img: This targets <img> elements within the "signature-component" class. It sets the position property to absolute, and sets left and top to 0, effectively positioning these images at the top-left corner of their parent elements.
*,
:before,
:after {
box-sizing: border-box;
}
html {
font: 18px "Helvetica Neue", sans-serif;
}
body {
text-align: center;
}
.signature-component {
text-align: left;
display: inline-block;
max-width: 100%;
}
.signature-component h1 {
margin-bottom: 0;
}
.signature-component h2 {
margin: 0;
font-size: 100%;
}
.signature-component button {
padding: 1em;
background: transparent;
box-shadow: 2px 2px 4px #777;
margin-top: 0.5em;
border: 1px solid #777;
font-size: 1rem;
}
.signature-component button.toggle {
background: rgba(255, 0, 0, 0.2);
}
.signature-component canvas {
display: block;
position: relative;
border: 1px solid;
}
.signature-component img {
position: absolute;
left: 0;
top: 0;
}
Step 3 (JavaScript Code):
Implement JavaScript to add interactivity to your signature pad. Allow users to draw, clear, and interact with the pad seamlessly.
The code is used for capturing and displaying digital signatures on a web page. Here's a breakdown of what the code does:
1. The code includes a comment block at the beginning to provide information about the library, its version, author, and license.
2. The code defines a SignaturePad class using an immediately invoked function expression (IIFE) that takes the document object as a parameter. This class is used to manage the signature pad functionality.
3. Inside the SignaturePad class, various options for configuring the signature pad are provided, such as the weight of velocity filtering, minimum and maximum line widths, pen color, background color, and more.
4. Event handlers are defined for mouse and touch input events, including mousedown, mousemove, mouseup, touchstart, touchmove, and touchend. These handlers are used to capture and update the signature as the user interacts with the pad.
5. The clear method is used to clear the signature pad and reset it to a blank state.
6. The showPointsToggle method allows toggling the display of individual points or marks as the user draws.
7. The toDataURL method converts the drawn signature to a data URL in the specified image type and quality.
8. The fromDataURL method loads a signature from a data URL and displays it on the signature pad.
9. Various internal methods are used for handling different aspects of the signature drawing process, such as _strokeUpdate, _strokeBegin, _strokeDraw, _strokeEnd, and others.
10. Event listeners for the save button, clear button, and a toggle button for showing/hiding points are defined.
11. The code concludes by creating a new instance of the SignaturePad class and attaching it to an HTML element with the ID "signature-pad." Event listeners for the save and clear buttons are also attached.
/*!
* Modified
* Signature Pad v1.5.3
* https://github.com/szimek/signature_pad
*
* Copyright 2016 Szymon Nowak
* Released under the MIT license
*/
var SignaturePad = (function(document) {
"use strict";
var log = console.log.bind(console);
var SignaturePad = function(canvas, options) {
var self = this,
opts = options || {};
this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
this.minWidth = opts.minWidth || 0.5;
this.maxWidth = opts.maxWidth || 2.5;
this.dotSize = opts.dotSize || function() {
return (self.minWidth + self.maxWidth) / 2;
};
this.penColor = opts.penColor || "black";
this.backgroundColor = opts.backgroundColor || "rgba(0,0,0,0)";
this.throttle = opts.throttle || 0;
this.throttleOptions = {
leading: true,
trailing: true
};
this.minPointDistance = opts.minPointDistance || 0;
this.onEnd = opts.onEnd;
this.onBegin = opts.onBegin;
this._canvas = canvas;
this._ctx = canvas.getContext("2d");
this._ctx.lineCap = 'round';
this.clear();
// we need add these inline so they are available to unbind while still having
// access to 'self' we could use _.bind but it's not worth adding a dependency
this._handleMouseDown = function(event) {
if (event.which === 1) {
self._mouseButtonDown = true;
self._strokeBegin(event);
}
};
var _handleMouseMove = function(event) {
event.preventDefault();
if (self._mouseButtonDown) {
self._strokeUpdate(event);
if (self.arePointsDisplayed) {
var point = self._createPoint(event);
self._drawMark(point.x, point.y, 5);
}
}
};
this._handleMouseMove = _.throttle(_handleMouseMove, self.throttle, self.throttleOptions);
//this._handleMouseMove = _handleMouseMove;
this._handleMouseUp = function(event) {
if (event.which === 1 && self._mouseButtonDown) {
self._mouseButtonDown = false;
self._strokeEnd(event);
}
};
this._handleTouchStart = function(event) {
if (event.targetTouches.length == 1) {
var touch = event.changedTouches[0];
self._strokeBegin(touch);
}
};
var _handleTouchMove = function(event) {
// Prevent scrolling.
event.preventDefault();
var touch = event.targetTouches[0];
self._strokeUpdate(touch);
if (self.arePointsDisplayed) {
var point = self._createPoint(touch);
self._drawMark(point.x, point.y, 5);
}
};
this._handleTouchMove = _.throttle(_handleTouchMove, self.throttle, self.throttleOptions);
//this._handleTouchMove = _handleTouchMove;
this._handleTouchEnd = function(event) {
var wasCanvasTouched = event.target === self._canvas;
if (wasCanvasTouched) {
event.preventDefault();
self._strokeEnd(event);
}
};
this._handleMouseEvents();
this._handleTouchEvents();
};
SignaturePad.prototype.clear = function() {
var ctx = this._ctx,
canvas = this._canvas;
ctx.fillStyle = this.backgroundColor;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height);
this._reset();
};
SignaturePad.prototype.showPointsToggle = function() {
this.arePointsDisplayed = !this.arePointsDisplayed;
};
SignaturePad.prototype.toDataURL = function(imageType, quality) {
var canvas = this._canvas;
return canvas.toDataURL.apply(canvas, arguments);
};
SignaturePad.prototype.fromDataURL = function(dataUrl) {
var self = this,
image = new Image(),
ratio = window.devicePixelRatio || 1,
width = this._canvas.width / ratio,
height = this._canvas.height / ratio;
this._reset();
image.src = dataUrl;
image.onload = function() {
self._ctx.drawImage(image, 0, 0, width, height);
};
this._isEmpty = false;
};
SignaturePad.prototype._strokeUpdate = function(event) {
var point = this._createPoint(event);
if(this._isPointToBeUsed(point)){
this._addPoint(point);
}
};
var pointsSkippedFromBeingAdded = 0;
SignaturePad.prototype._isPointToBeUsed = function(point) {
// Simplifying, De-noise
if(!this.minPointDistance)
return true;
var points = this.points;
if(points && points.length){
var lastPoint = points[points.length-1];
if(point.distanceTo(lastPoint) < this.minPointDistance){
// log(++pointsSkippedFromBeingAdded);
return false;
}
}
return true;
};
SignaturePad.prototype._strokeBegin = function(event) {
this._reset();
this._strokeUpdate(event);
if (typeof this.onBegin === 'function') {
this.onBegin(event);
}
};
SignaturePad.prototype._strokeDraw = function(point) {
var ctx = this._ctx,
dotSize = typeof(this.dotSize) === 'function' ? this.dotSize() : this.dotSize;
ctx.beginPath();
this._drawPoint(point.x, point.y, dotSize);
ctx.closePath();
ctx.fill();
};
SignaturePad.prototype._strokeEnd = function(event) {
var canDrawCurve = this.points.length > 2,
point = this.points[0];
if (!canDrawCurve && point) {
this._strokeDraw(point);
}
if (typeof this.onEnd === 'function') {
this.onEnd(event);
}
};
SignaturePad.prototype._handleMouseEvents = function() {
this._mouseButtonDown = false;
this._canvas.addEventListener("mousedown", this._handleMouseDown);
this._canvas.addEventListener("mousemove", this._handleMouseMove);
document.addEventListener("mouseup", this._handleMouseUp);
};
SignaturePad.prototype._handleTouchEvents = function() {
// Pass touch events to canvas element on mobile IE11 and Edge.
this._canvas.style.msTouchAction = 'none';
this._canvas.style.touchAction = 'none';
this._canvas.addEventListener("touchstart", this._handleTouchStart);
this._canvas.addEventListener("touchmove", this._handleTouchMove);
this._canvas.addEventListener("touchend", this._handleTouchEnd);
};
SignaturePad.prototype.on = function() {
this._handleMouseEvents();
this._handleTouchEvents();
};
SignaturePad.prototype.off = function() {
this._canvas.removeEventListener("mousedown", this._handleMouseDown);
this._canvas.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
this._canvas.removeEventListener("touchstart", this._handleTouchStart);
this._canvas.removeEventListener("touchmove", this._handleTouchMove);
this._canvas.removeEventListener("touchend", this._handleTouchEnd);
};
SignaturePad.prototype.isEmpty = function() {
return this._isEmpty;
};
SignaturePad.prototype._reset = function() {
this.points = [];
this._lastVelocity = 0;
this._lastWidth = (this.minWidth + this.maxWidth) / 2;
this._isEmpty = true;
this._ctx.fillStyle = this.penColor;
};
SignaturePad.prototype._createPoint = function(event) {
var rect = this._canvas.getBoundingClientRect();
return new Point(
event.clientX - rect.left,
event.clientY - rect.top
);
};
SignaturePad.prototype._addPoint = function(point) {
var points = this.points,
c2, c3,
curve, tmp;
points.push(point);
if (points.length > 2) {
// To reduce the initial lag make it work with 3 points
// by copying the first point to the beginning.
if (points.length === 3) points.unshift(points[0]);
tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
c2 = tmp.c2;
tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
c3 = tmp.c1;
curve = new Bezier(points[1], c2, c3, points[2]);
this._addCurve(curve);
// Remove the first element from the list,
// so that we always have no more than 4 points in points array.
points.shift();
}
};
SignaturePad.prototype._calculateCurveControlPoints = function(s1, s2, s3) {
var dx1 = s1.x - s2.x,
dy1 = s1.y - s2.y,
dx2 = s2.x - s3.x,
dy2 = s2.y - s3.y,
m1 = {
x: (s1.x + s2.x) / 2.0,
y: (s1.y + s2.y) / 2.0
},
m2 = {
x: (s2.x + s3.x) / 2.0,
y: (s2.y + s3.y) / 2.0
},
l1 = Math.sqrt(1.0 * dx1 * dx1 + dy1 * dy1),
l2 = Math.sqrt(1.0 * dx2 * dx2 + dy2 * dy2),
dxm = (m1.x - m2.x),
dym = (m1.y - m2.y),
k = l2 / (l1 + l2),
cm = {
x: m2.x + dxm * k,
y: m2.y + dym * k
},
tx = s2.x - cm.x,
ty = s2.y - cm.y;
return {
c1: new Point(m1.x + tx, m1.y + ty),
c2: new Point(m2.x + tx, m2.y + ty)
};
};
SignaturePad.prototype._addCurve = function(curve) {
var startPoint = curve.startPoint,
endPoint = curve.endPoint,
velocity, newWidth;
velocity = endPoint.velocityFrom(startPoint);
velocity = this.velocityFilterWeight * velocity +
(1 - this.velocityFilterWeight) * this._lastVelocity;
newWidth = this._strokeWidth(velocity);
this._drawCurve(curve, this._lastWidth, newWidth);
this._lastVelocity = velocity;
this._lastWidth = newWidth;
};
SignaturePad.prototype._drawPoint = function(x, y, size) {
var ctx = this._ctx;
ctx.moveTo(x, y);
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
this._isEmpty = false;
};
SignaturePad.prototype._drawMark = function(x, y, size) {
var ctx = this._ctx;
ctx.save();
ctx.moveTo(x, y);
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
ctx.fill();
ctx.restore();
};
SignaturePad.prototype._drawCurve = function(curve, startWidth, endWidth) {
var ctx = this._ctx,
widthDelta = endWidth - startWidth,
drawSteps, width, i, t, tt, ttt, u, uu, uuu, x, y;
drawSteps = Math.floor(curve.length());
ctx.beginPath();
for (i = 0; i < drawSteps; i++) {
// Calculate the Bezier (x, y) coordinate for this step.
t = i / drawSteps;
tt = t * t;
ttt = tt * t;
u = 1 - t;
uu = u * u;
uuu = uu * u;
x = uuu * curve.startPoint.x;
x += 3 * uu * t * curve.control1.x;
x += 3 * u * tt * curve.control2.x;
x += ttt * curve.endPoint.x;
y = uuu * curve.startPoint.y;
y += 3 * uu * t * curve.control1.y;
y += 3 * u * tt * curve.control2.y;
y += ttt * curve.endPoint.y;
width = startWidth + ttt * widthDelta;
this._drawPoint(x, y, width);
}
ctx.closePath();
ctx.fill();
};
SignaturePad.prototype._strokeWidth = function(velocity) {
return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
};
var Point = function(x, y, time) {
this.x = x;
this.y = y;
this.time = time || new Date().getTime();
};
Point.prototype.velocityFrom = function(start) {
return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 1;
};
Point.prototype.distanceTo = function(start) {
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
};
var Bezier = function(startPoint, control1, control2, endPoint) {
this.startPoint = startPoint;
this.control1 = control1;
this.control2 = control2;
this.endPoint = endPoint;
};
// Returns approximated length.
Bezier.prototype.length = function() {
var steps = 10,
length = 0,
i, t, cx, cy, px, py, xdiff, ydiff;
for (i = 0; i <= steps; i++) {
t = i / steps;
cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
if (i > 0) {
xdiff = cx - px;
ydiff = cy - py;
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
px = cx;
py = cy;
}
return length;
};
Bezier.prototype._point = function(t, start, c1, c2, end) {
return start * (1.0 - t) * (1.0 - t) * (1.0 - t) +
3.0 * c1 * (1.0 - t) * (1.0 - t) * t +
3.0 * c2 * (1.0 - t) * t * t +
end * t * t * t;
};
return SignaturePad;
})(document);
var signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
backgroundColor: 'rgba(255, 255, 255, 0)',
penColor: 'rgb(0, 0, 0)',
velocityFilterWeight: .7,
minWidth: 0.5,
maxWidth: 2.5,
throttle: 16, // max x milli seconds on event update, OBS! this introduces lag for event update
minPointDistance: 3,
});
var saveButton = document.getElementById('save'),
clearButton = document.getElementById('clear'),
showPointsToggle = document.getElementById('showPointsToggle');
saveButton.addEventListener('click', function(event) {
var data = signaturePad.toDataURL('image/png');
window.open(data);
});
clearButton.addEventListener('click', function(event) {
signaturePad.clear();
});
showPointsToggle.addEventListener('click', function(event) {
signaturePad.showPointsToggle();
showPointsToggle.classList.toggle('toggle');
});
Final Output:
Conclusion:
Incorporating a signature pad into your web application using HTML, CSS, and JavaScript can significantly enhance user experiences and improve the functionality of your site. With the provided source code and this comprehensive guide, you're well on your way to implementing a digital signature solution that will impress your users and streamline your web development projects. Start enhancing your web apps today!
Code by: Kunuk Nykjær
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊