Design and Implementation of a Mobile Signature Component Using HTML5 Canvas
This article details the complete design and implementation process of a mobile signature component using HTML5 Canvas, covering requirements, architecture, event handling for touch and mouse, landscape mode handling, drawing optimization, and file export techniques for both base64 and File formats.
The front‑end is the visible façade of a software system, and a mobile signature component is a crucial part of it. This article walks through the entire development lifecycle of such a component, from requirement analysis to final polishing.
Requirements include supporting vertical signatures, re‑sign and cancel actions, horizontal signatures for left‑to‑right writing habits, and compatibility with Android browsers, iOS browsers, DingTalk, WeChat, and other environments.
Design Overview – Because the component must run across many environments, a pure H5 solution is chosen. The Canvas API is sufficient for the drawing needs. The basic idea is to listen to Touch Events on mobile devices and Mouse Events on desktops, draw the trajectory on a Canvas, clear the canvas for re‑sign, and export the result as either a Base64 string or a File.
Implementation Challenges involve abstracting Touch and Mouse events into a unified interface, handling "fake" landscape mode when the device is locked in portrait, converting coordinates between the screen and the rotated canvas, and ensuring the exported image matches what the user sees.
4.1 Basic Functionality
4.1.1 Event Unification
isSupportTouch: 'ontouchstart' in window,
events: 'ontouchstart' in window ?
['touchstart', 'touchmove', 'touchend', 'touchleave'] :
['mousedown', 'mousemove', 'mouseup', 'mouseleave'];
this.$refs.canvas.addEventListener(this.events[0], this.startEventHandler, false);
this.$refs.canvas.addEventListener(this.events[1], this.moveEventHandler, false);
this.$refs.canvas.addEventListener(this.events[2], this.endEventHandler, false);
this.$refs.canvas.addEventListener(this.events[3], this.leaveEventHandler, false);
const evt = this.isSupportTouch ? event.touches[0] : event;4.1.2 Drawing Strokes
moveEventHandler(event) {
event.preventDefault();
const evt = this.isSupportTouch ? event.touches[0] : event;
const coverPos = canvas.getBoundingClientRect();
const mouseX = evt.clientX - coverPos.left;
const mouseY = evt.clientY - coverPos.top;
const position = {x: mouseX, y: mouseY}; // get current event position
this.ctx.lineTo(position.x, position.y);
this.ctx.stroke();
},4.1.3 File Export
dataURL = canvas.toDataURL('image/png'); export function canvas2File(canvas: HTMLCanvasElement, fileName?: string, lastModified?: number, type = "image/jpeg", quality = 0.8): Promise
{
return new Promise
((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
reject(new Error('canvas文件导出失败'));
} else {
const file = new File([blob], fileName, { lastModified, type });
resolve(file);
}
}, type, quality);
});
}4.2 Tackling Difficulties
4.2.1 Fake Landscape – The canvas is rotated 90° with CSS transform while the event coordinates remain unchanged. The conversion formulas are:
canvas new Y = canvas.width - old screen X;
canvas new X = old screen Y;4.2.2 Coordinate Conversion
export const getForceLandscapeEventPosition = (evt: MouseEvent, canvas: HTMLCanvasElement, forceLandscape: boolean): EventPosition => {
const coverPos = canvas.getBoundingClientRect();
const mouseX = evt.clientX - coverPos.left;
const mouseY = evt.clientY - coverPos.top;
const isReverseXY = forceLandscape && (window.orientation == null || window.orientation === 180 || window.orientation === 0);
if (isReverseXY) {
const { clientWidth } = document.documentElement;
const width = clientWidth;
return {x: mouseY, y: (width - mouseX)};
} else {
return {x: mouseX, y: mouseY};
}
};Usage in the move handler:
const evt = this.isSupportTouch ? event.touches[0] : event;
const position = getForceLandscapeEventPosition(evt, this.$refs.canvas, !this.allowPortrait);
this.ctx.lineTo(position.x, position.y);
this.ctx.stroke();4.2.3 Screen Rotation Handling – On resize/orientation change the canvas is cleared to avoid stretching or truncation:
window.addEventListener("resize", (e) => {
this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
this.ctx.closePath();
});CSS transformation for fake landscape:
const width = clientWidth - leftOffset;
const height = clientHeight - topOffset;
const longSide = Math.max(width, height);
const shortSide = Math.min(width, height);
resultStyle.transform = `rotate(90deg)`;
resultStyle.width = `${longSide}px`;
resultStyle.height = `${shortSide}px`;
resultStyle.transformOrigin = `${shortSide / 2}px center`;4.3 Refinements
The component’s API was improved to export both File and Base64 data, allow disabling the fake landscape mode, and set a default background for JPEG outputs. The author also referenced the open‑source signature_pad library, which uses cubic Bézier curves for smoother strokes.
5 Summary
By planning the solution early, understanding the challenges, and applying techniques such as coordinate conversion and Bézier‑based smoothing, the author delivered a robust mobile signature component and gained deeper insight into front‑end design patterns.
Zhengtong Technical Team
How do 700+ nationwide projects deliver quality service? What inspiring stories lie behind dozens of product lines? Where is the efficient solution for tens of thousands of customer needs each year? This is Zhengtong Digital's technical practice sharing—a bridge connecting engineers and customers!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.