HTML canvas can be used for sketching. In this blog post, I would like to quickly discuss how to use JavaScript to implement a canvas with touch sketching.
Touch Canvas
The touch canvas can be used with any devices that use touch screen.
Line width :
Color :
Source Code
The source code was modified based on the Mozilla touch drawing example with some bug fixes and feature enhancement.
<script> const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); const viewport = window.visualViewport; var offsetX; var offsetY; functionstartup() { canvas.addEventListener('touchstart', handleStart); canvas.addEventListener('touchend', handleEnd); canvas.addEventListener('touchcancel', handleCancel); canvas.addEventListener('touchmove', handleMove); } document.addEventListener("DOMContentLoaded", startup); const ongoingTouches = []; functionhandleStart(evt) { evt.preventDefault(); const touches = evt.changedTouches; offsetX = canvas.getBoundingClientRect().left; offsetY = canvas.getBoundingClientRect().top; console.log(offsetX, offsetY) for (let i = 0; i < touches.length; i++) { ongoingTouches.push(copyTouch(touches[i])); } } functionhandleMove(evt) { evt.preventDefault(); const touches = evt.changedTouches; for (let i = 0; i < touches.length; i++) { const color = document.getElementById('selColor').value; const idx = ongoingTouchIndexById(touches[i].identifier); if (idx >= 0) { context.beginPath(); context.moveTo(ongoingTouches[idx].clientX - offsetX, ongoingTouches[idx].clientY - offsetY); context.lineTo(touches[i].clientX - offsetX, touches[i].clientY - offsetY); context.lineWidth = document.getElementById('selWidth').value; context.strokeStyle = color; context.lineJoin = "round"; context.closePath(); context.stroke(); ongoingTouches.splice(idx, 1, copyTouch(touches[i])); // swap in the new touch record } } } functionhandleEnd(evt) { evt.preventDefault(); const touches = evt.changedTouches; for (let i = 0; i < touches.length; i++) { const color = document.getElementById('selColor').value; let idx = ongoingTouchIndexById(touches[i].identifier); if (idx >= 0) { context.lineWidth = document.getElementById('selWidth').value; context.fillStyle = color; ongoingTouches.splice(idx, 1); // remove it; we're done } } } functionhandleCancel(evt) { evt.preventDefault(); const touches = evt.changedTouches; for (let i = 0; i < touches.length; i++) { let idx = ongoingTouchIndexById(touches[i].identifier); ongoingTouches.splice(idx, 1); // remove it; we're done } } functioncopyTouch({ identifier, clientX, clientY }) { return { identifier, clientX, clientY }; } functionongoingTouchIndexById(idToFind) { for (let i = 0; i < ongoingTouches.length; i++) { const id = ongoingTouches[i].identifier; if (id === idToFind) { return i; } } return -1; // not found } functionclearArea() { context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, context.canvas.width, context.canvas.height); } </script>
Caveats
The coordinate computation in the canvas is very confusing and error-prone, especially when the HTML layout is complicated and there was scroll happened. For the best practice, we should recompute the offsets when the touch event starts.
1 2 3 4 5 6 7 8 9 10
functionhandleStart(evt) { evt.preventDefault(); const touches = evt.changedTouches; offsetX = canvas.getBoundingClientRect().left; offsetY = canvas.getBoundingClientRect().top; console.log(offsetX, offsetY) for (let i = 0; i < touches.length; i++) { ongoingTouches.push(copyTouch(touches[i])); } }
Notice that the offsets that getBoundingClientRect returns is the coordinates relative to the viewport. When we try to obtain the coordinates of the touch event, we should use clientX and clientY which are also relative to the viewport, so that the touch event coordinates and canvas offsets are relative to the same object, and the touch event coordinates on the canvas can be computed correctly.