Electronic Signature Implementation (Drawing/Input) on a Website
Developers face a typical problem: a user on a mobile device tries to draw a signature with a finger, but the canvas does not handle touches correctly. Or on Retina displays the signature comes out blurry, and on old Android tablets—with a delay of up to 300 ms. We solved this problem in several projects—from data processing consents to acceptance certificates for 5+ clients. We'll tell you how to implement three signature input methods: drawing, printed text, and image upload.
Do not confuse with a qualified electronic signature (QES): here we are talking about a visual signature without cryptography, used in contracts, consents, acts. For legal force, additional infrastructure is required—we also implement that turnkey. Get a consultation to evaluate your project.
Why canvas is the best choice for drawing signatures?
Canvas provides low latency (up to 16 ms at 60 FPS), Retina adaptation, and correct stylus handling. Unlike SVG, canvas does not create DOM nodes with each stroke, which is critical for long sessions—canvas performance is 3 times higher on 1000+ strokes. The alternative—jSignature—does not support Retina and is heavier to customize.
Three signature input options
Drawing — the user draws a signature with a mouse or stylus on canvas. The most familiar format for most users. Printed text — enter a name, displayed in a chosen "handwritten" font (Caveat, Dancing Script, Pacifico). Image upload — upload a photo/scan of your own signature.
Let's compare the methods:
| Method | Mobile Convenience | Image Quality | Compatibility |
|---|---|---|---|
| Drawing (canvas) | Excellent (with touch-none) | High (Retina adaptation) | All browsers |
| Printed text | Excellent (keyboard) | Medium (depends on font) | All browsers |
| Image upload | Good (gallery) | Depends on source | All browsers |
Now let's compare libraries for canvas signature:
| Library | Size (min) | Touch Support | Retina Adaptation |
|---|---|---|---|
| signature_pad | 8 KB | Yes | Yes |
| jSignature | 12 KB | Yes | No |
| Fabric.js | 300+ KB | Yes | Partial |
Implementation via signature_pad
The signature_pad library is the standard for canvas signatures:
import SignaturePad from 'signature_pad';
import { useRef, useEffect, useState } from 'react';
function SignatureCanvas({ onSave, onClear }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const padRef = useRef<SignaturePad | null>(null);
useEffect(() => {
const canvas = canvasRef.current!;
padRef.current = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)',
penColor: 'rgb(0, 0, 0)',
minWidth: 0.5,
maxWidth: 2.5,
throttle: 16, // ~60fps
});
// Scale for Retina displays
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
const ctx = canvas.getContext('2d')!;
ctx.scale(ratio, ratio);
padRef.current!.clear();
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
return () => window.removeEventListener('resize', resizeCanvas);
}, []);
const handleSave = () => {
if (padRef.current!.isEmpty()) return;
const dataUrl = padRef.current!.toDataURL('image/png');
onSave(dataUrl);
};
return (
<div className="border-2 border-dashed border-gray-300 rounded-lg">
<canvas
ref={canvasRef}
className="w-full h-40 touch-none" // touch-none is important for mobile
/>
<div className="flex gap-2 p-2">
<button onClick={() => { padRef.current!.clear(); onClear(); }}>Clear</button>
<button onClick={handleSave} className="btn-primary">Sign</button>
</div>
</div>
);
}
Text signature input
const SIGNATURE_FONTS = [
{ name: 'Caveat', label: 'Handwritten 1' },
{ name: 'Dancing Script', label: 'Handwritten 2' },
{ name: 'Pacifico', label: 'Calligraphy' },
];
function TextSignature({ name, onSave }) {
const [selectedFont, setSelectedFont] = useState(SIGNATURE_FONTS[0]);
const canvasRef = useRef<HTMLCanvasElement>(null);
const renderToCanvas = (font: string, text: string) => {
const canvas = canvasRef.current!;
const ctx = canvas.getContext('2d')!;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = `48px "${font}"`;
ctx.fillStyle = '#1a1a1a';
ctx.fillText(text, 20, 70);
return canvas.toDataURL('image/png');
};
return (
<div>
<div className="flex gap-2 mb-3">
{SIGNATURE_FONTS.map(font => (
<button
key={font.name}
style={{ fontFamily: font.name }}
onClick={() => setSelectedFont(font)}
className={selectedFont.name === font.name ? 'border-blue-500 border-2' : ''}
>
{name}
</button>
))}
</div>
<canvas ref={canvasRef} width={400} height={100} className="border rounded" />
<button onClick={() => onSave(renderToCanvas(selectedFont.name, name))}>
Apply signature
</button>
</div>
);
}
How to embed the signature into PDF and other documents?
After receiving the signature as a base64 PNG, we embed it into the document. For PDF—via PDF-lib:
import { PDFDocument } from 'pdf-lib';
async function embedSignatureInPdf(pdfBytes, signatureDataUrl, position) {
const pdfDoc = await PDFDocument.load(pdfBytes);
const pages = pdfDoc.getPages();
const targetPage = pages[position.page];
const signatureBytes = Buffer.from(signatureDataUrl.split(',')[1], 'base64');
const signatureImage = await pdfDoc.embedPng(signatureBytes);
targetPage.drawImage(signatureImage, {
x: position.x,
y: position.y,
width: position.width,
height: position.height,
});
return await pdfDoc.save();
}
Storage and audit
The signature as an image is stored in S3. The database records: user_id, timestamp, IP address, user-agent, document_id, document hash. This provides a basic audit trail. Additionally, you can add an invisible watermark with metadata—it does not degrade readability but allows restoring the owner when editing the file.
Why touch-none is critical for mobile devices?
Without touch-none, the browser interprets the touch as a page scroll, not drawing on the canvas. This leads to delays and false triggers. We set the CSS property touch-action: none on the canvas, giving priority to drawing. On mobile devices, the latency drops from 300 ms to 16 ms—a noticeable difference for the user.
How to ensure legal significance of the signature?
A visual signature has no legal force without additional measures. We record the time, IP, user-agent, and document hash at the moment of signing. If full legal validity is required, we add integration with QES (qualified electronic signature)—this increases the timeline to 5 days but ensures compliance with 63-FZ (Russian Federal Law).
Work process
- Analytics—determine signature requirements (legal force, number of signatories).
- Design—select input methods, widget design.
- Implementation—write components in React/TypeScript using signature_pad and pdf-lib.
- Testing—verify on iOS, Android, desktop, with various resolutions.
- Deploy—configure S3 storage, add audit logs.
Indicative timeline
A component with three signature methods (drawing, text, upload) and PDF embedding—from 2 to 5 days depending on integration complexity. Cost is calculated individually.
What is included in the work
- Ready React signature component with three input methods.
- Integration with PDF-lib for embedding signatures into documents.
- Signature storage in S3 with audit log.
- API and setup documentation.
- Team training (1 hour online).
Typical implementation mistakes
- Forgetting
touch-noneon canvas—signature won't work on mobile. - Not scaling for Retina—blurry signature.
- Using SVG instead of canvas—performance drop on long sessions.
We have implemented electronic signatures for 5+ projects, including an online housing rental service and a contract platform. We guarantee post-implementation support. Order electronic signature implementation on your website—from simple consent to full document workflow. Get a consultation, and we will prepare a commercial offer within 24 hours.







