At work, they needed a quick and dirty example to hand-sign a document and append it to JSON as a base64 Date URL. It works all fine and dandy in itself, but my own curiosity got the better of me and I was annoyed to find out, that the area of the buttons appeared to be much larger on mobile than they looked like. But even inspecting buttons in my example project didn’t help me a lot to pin down the problems.
I did add a snippet as well. Don’t forget, it’s only working on mobile browsers or if you use emulation on your chrome browser, for example.
"use strict";
const canvas = document.getElementById('sign');
const context = canvas.getContext('2d');
context.lineCap = 'round';
context.lineJoin = 'round';
context.strokeStyle = 'black';
context.lineWidth = 2;
let points = [];
let isPainting = false;
setup();
function setup() {
if (isMobile() || isIpad()) {
setupMobile();
} else {
// Some other stuff that was supposed to happen, but was canned
return;
}
}
function setupMobile() {
canvas.addEventListener('touchstart', (e) => {
addToDrawingPointsTouch(canvas, e);
if (!isPainting) {
isPainting = !isPainting;
}
const ctx = context;
// Not the most elegant solution for the initial call, but touchmove takes a bit to trigger
const initialStarting = points[0];
ctx.beginPath();
ctx.moveTo(initialStarting.x, initialStarting.y);
ctx.lineTo(initialStarting.x, initialStarting.y);
ctx.stroke();
}, { passive: true })
canvas.addEventListener('touchmove', (e) => {
if (isPainting) {
addToDrawingPointsTouch(canvas, e);
const ctx = context;
points.forEach((v, index) => {
if (points[index + 1]) {
ctx.beginPath();
ctx.moveTo(v.x, v.y);
ctx.lineTo(points[index + 1]?.x, points[index + 1]?.y);
ctx.stroke();
}
})
}
}, { passive: true })
canvas.addEventListener('touchend', () => {
if (isPainting) {
isPainting = !isPainting;
points = [];
}
}, { passive: true })
}
function addToDrawingPointsTouch(canvas, mouseEvent) {
const rect = canvas.getBoundingClientRect();
points.push({
x: (mouseEvent.touches[0].clientX - rect.left) / (rect.right - rect.left) * canvas.width,
y: (mouseEvent.touches[0].clientY - rect.top) / (rect.bottom - rect.top) * canvas.height
});
}
function isMobile() {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
return true
} else {
return false;
}
}
function isIpad() {
if (navigator.userAgent.match(/Mac/) && navigator.maxTouchPoints && navigator.maxTouchPoints > 2) {
return true;
}
else {
false;
}
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
function createCanvasPngDataUrl() {
return canvas.toDataURL('image/png');
}
body {
user-select: none;
-webkit-user-select: none;
touch-action: none;
-ms-touch-action: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='main.css'>
<script defer src='main.js'></script>
</head>
<body>
<form id="form">
<canvas style="border: 1px solid black;" id="sign">
</canvas>
</form>
<div id="menu">
<button onclick="createCanvasPngDataUrl()">Create DataUrl</button>
<button onclick="clearCanvas()" id="clear">Clear</button>
</div>
</body>
</html>