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/signature_pad@4.1.7/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>
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