How to make a signature pad canvas in signature-pad.js work responsively?
My challenge is the following:
-
I either get the cursor positioning while drawing correctly but lose the input on resize
// OR //
-
I save the input while resizing but then the canvas is not calculated correctly and it does not track the cursor position anymore
Right now I am sure the issue comes from this line in my code:
signaturePad.fromData(signaturePad.toData());
Commenting this out or not decides the outcome of my two observed behaviors.
Does someone have an easy solution to this? I am getting crazy about it already. I also created a JS Fiddle for you to see what I have so far:
https://jsfiddle.net/xetrzi9/hort1ubj/3/
Additional info:
When scrolling on the phone it also resets the input for some reason which is really weird.
function initializeSignaturePad() {
const wrapper = document.querySelector('.form_signature-wrapper');
const canvas = wrapper.querySelector('#signature-pad');
const clearButton = wrapper.querySelector('[data-action=clear]');
let signaturePad;
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
const rect = wrapper.getBoundingClientRect();
canvas.width = rect.width * ratio;
canvas.height = rect.height * ratio;
canvas.getContext('2d').scale(ratio, ratio);
// signaturePad.fromData(signaturePad.toData());
}
function initPad() {
signaturePad = new SignaturePad(canvas, {
penColor: 'rgb(255, 255, 255)',
minWidth: 0.5,
maxWidth: 2.5,
throttle: 0,
minDistance: 0,
velocityFilterWeight: 0.4,
});
updateMousePosition();
}
function updateMousePosition() {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
function getPointFromEvent(event) {
let x, y;
if (event.touches && event.touches.length > 0) {
const touch = event.touches[0];
x = touch.clientX;
y = touch.clientY;
} else {
x = event.clientX;
y = event.clientY;
}
x = (x - rect.left) * scaleX;
y = (y - rect.top) * scaleY;
return new SignaturePad.Point(x, y);
}
const originalOnMouseDown = signaturePad._handleMouseDown;
signaturePad._handleMouseDown = function(event) {
const point = getPointFromEvent(event);
originalOnMouseDown.call(signaturePad, event, point);
};
const originalOnMouseMove = signaturePad._handleMouseMove;
signaturePad._handleMouseMove = function(event) {
const point = getPointFromEvent(event);
originalOnMouseMove.call(signaturePad, event, point);
};
const originalOnTouchStart = signaturePad._handleTouchStart;
signaturePad._handleTouchStart = function(event) {
const point = getPointFromEvent(event);
originalOnTouchStart.call(signaturePad, event, point);
};
const originalOnTouchMove = signaturePad._handleTouchMove;
signaturePad._handleTouchMove = function(event) {
const point = getPointFromEvent(event);
originalOnTouchMove.call(signaturePad, event, point);
};
}
initPad();
resizeCanvas();
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
window.addEventListener(
'resize',
debounce(() => {
resizeCanvas();
}, 250)
);
clearButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
signaturePad.clear();
});
return signaturePad;
}
document.addEventListener('DOMContentLoaded', function() {
initializeSignaturePad();
});
html,
body {
margin: 0;
padding: 0;
}
body {
background: #0c0c0c;
font-family: sans-serif;
}
.form_field-wrapper {
max-width: 600px;
margin: 0 auto;
padding: 2rem 0;
}
.form_label {
color: #fff;
margin-bottom: 0.5rem;
}
.form_signature-wrapper {
width: 100%;
height: auto;
aspect-ratio: 5/3;
}
.form_signature-canvas {
height: 100%;
width: 100%;
display: block;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.25rem 0.25rem 0 0;
}
.form_signature-wrapper {
position: relative;
}
.form_signature-wrapper button {
appearance: none;
outline: none;
border: none;
box-shadow: none;
font-weight: 600;
color: #fff;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem;
border-radius: 0.25rem;
position: absolute;
top: 0.75rem;
right: 0.75rem;
transition: 100ms ease-in-out background;
}
.form_signature-wrapper button:hover {
background: rgba(255, 255, 255, 0.2);
cursor: pointer;
}
.form_info-wrapper {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 0 0 0.25rem 0.25rem;
font-size: 0.875rem;
}
.form_info-icon {
height: 1rem;
width: 1rem;
}
.form_info-icon svg {
display: flex;
height: auto;
width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/signature_pad.umd.min.js"></script>
<div class="form_field-wrapper">
<div class="form_label">Signature</div>
<div class="form_signature-wrapper">
<canvas id="signature-pad" class="form_signature-canvas" width="" height="" style="touch-action: none"></canvas
><button
data-action="clear"
type="button"
class="form_signature-btn is-clear">
Delete
</button>
</div>
<div class="form_info-wrapper is-signature form_label">
<div class="form_info-icon">
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14">
<path
d="M7 14a7 7 0 1 1 7-7 7.007 7.007 0 0 1-7 7ZM7 1.167A5.833 5.833 0 1 0 12.833 7 5.84 5.84 0 0 0 7 1.167Z"
fill="currentColor"></path>
<path
d="M7.583 8.75H6.416v-.434a2.306 2.306 0 0 1 1.146-2.041A1.167 1.167 0 1 0 5.833 5.25H4.666a2.333 2.333 0 1 1 3.46 2.044 1.156 1.156 0 0 0-.543 1.022v.434ZM7.583 9.916H6.416v1.167h1.167V9.916Z"
fill="currentColor"></path>
</svg>
</div>
<div class="form_info">Please add your signature</div>
</div>
</div>
2
Answers
I ended up building my own little library that also integrates with Webflow components super easily.
If you want to check it out, you can find it here:
https://github.com/vierless/attributes/tree/main/signature-pad
Seems like the clearing of the canvas on resize is intended by the library.
Certainly, an questionable design decision but you could bypass this behavior by creating your own point calculation adding a scaling logic.
Basically we’re setting the canvas dimensions only on initialization (according to CSS rules) and scale/project the mouse/pointer coordinates to meet the current canvas scaling. So the canvas keeps its dimensions and is solely scaled by its CSS layout properties.