Розробка форми з полем підпису (Signature Field) на сайті
Поле для рукописної підпису використовується в юридичних документах, актах приймання-передачі, онлайн-контрактах. Реалізація виглядає просто, але деталей багато: різні пристрої, збереження в різних форматах, юридична значимість.
Сценарії застосування
- Підписання контрактів та угод прямо в браузері
- Акти виконаних робіт в CRM/ERP
- Згода на обробку персональних даних з доказовою базою
- Медичні форми, страхові заяви
- Кур'єрські додатки (підтвердження доставки)
Як це працює технічно
Підпис малюється на елементі <canvas>. Обробники подій стежать за mousedown/mousemove/mouseup на десктопі та touchstart/touchmove/touchend на мобільних. Підсумкове зображення експортується через canvas.toDataURL('image/png') або конвертується в SVG-шлях для масштабованості.
class SignaturePad {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.points = [];
this.isDrawing = false;
this._setupEvents();
this._setupCanvas();
}
_setupCanvas() {
// Підтримка HiDPI
const ratio = window.devicePixelRatio || 1;
const rect = this.canvas.getBoundingClientRect();
this.canvas.width = rect.width * ratio;
this.canvas.height = rect.height * ratio;
this.ctx.scale(ratio, ratio);
this.ctx.strokeStyle = '#1a1a1a';
this.ctx.lineWidth = 2;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
}
_getPos(e) {
const rect = this.canvas.getBoundingClientRect();
const src = e.touches ? e.touches[0] : e;
return {
x: src.clientX - rect.left,
y: src.clientY - rect.top,
};
}
_setupEvents() {
this.canvas.addEventListener('mousedown', (e) => this._start(e));
this.canvas.addEventListener('mousemove', (e) => this._draw(e));
this.canvas.addEventListener('mouseup', () => this._end());
this.canvas.addEventListener('touchstart', (e) => { e.preventDefault(); this._start(e); }, { passive: false });
this.canvas.addEventListener('touchmove', (e) => { e.preventDefault(); this._draw(e); }, { passive: false });
this.canvas.addEventListener('touchend', () => this._end());
}
_start(e) {
this.isDrawing = true;
const pos = this._getPos(e);
this.ctx.beginPath();
this.ctx.moveTo(pos.x, pos.y);
this.points.push({ x: pos.x, y: pos.y, type: 'start' });
}
_draw(e) {
if (!this.isDrawing) return;
const pos = this._getPos(e);
this.ctx.lineTo(pos.x, pos.y);
this.ctx.stroke();
this.points.push({ x: pos.x, y: pos.y, type: 'draw' });
}
_end() {
this.isDrawing = false;
}
isEmpty() {
const data = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data;
return !data.some((v, i) => i % 4 === 3 && v > 0);
}
toDataURL(type = 'image/png') {
return this.canvas.toDataURL(type);
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.points = [];
}
}
Готові бібліотеки
Якщо немає сенсу писати з нуля — signature_pad від Szimek (MIT, ~4 KB gzip). Підтримує згладжування кривих Безьє, експорт SVG, відновлення підпису з даних.
import SignaturePad from 'signature_pad';
const pad = new SignaturePad(document.getElementById('sig-canvas'), {
minWidth: 0.5,
maxWidth: 2.5,
penColor: '#000',
backgroundColor: 'rgba(0,0,0,0)', // прозорий фон для PNG
});
// Збереження
const dataURL = pad.toDataURL('image/svg+xml');
// Відновлення
pad.fromData(savedPointsArray);
Формати зберігання
| Формат | Плюси | Мінуси |
|---|---|---|
| PNG (base64) | Простота, сумісність | Великий розмір, не масштабується |
| SVG | Масштабується, редагується | Можна підробити |
| JSON (точки) | Повне відновлення, мінімум даних | Потребує рендеринг для відображення |
| PDF (вбудований) | Юридично значимо | Складніше в реалізації |
Для юридично значимих документів рекомендується зберігати JSON з точками + метаданні (timestamp, IP, user-agent, хеш документа).
Вбудовування в PDF
Для генерації підписаних PDF використовуємо pdf-lib (Node.js/браузер):
import { PDFDocument } from 'pdf-lib';
async function embedSignature(pdfBytes, signatureDataURL) {
const pdfDoc = await PDFDocument.load(pdfBytes);
const pages = pdfDoc.getPages();
const page = pages[pages.length - 1];
const pngBytes = await fetch(signatureDataURL).then(r => r.arrayBuffer());
const img = await pdfDoc.embedPng(pngBytes);
page.drawImage(img, {
x: 50,
y: 50,
width: 200,
height: 80,
});
return await pdfDoc.save();
}
Мобільні пристрої
На сенсорному екрані потрібно блокувати скрол під час малювання (touch-action: none на canvas), інакше сторінка прокручується замість малювання. Apple Pencil та стилуси працюють через ті ж Touch Events, але touchForce доступна тільки в Safari.
Терміни
Базове поле підпису з очисткою, валідацією та збереженням PNG — 2–3 робочих дні. Інтеграція з генерацією PDF, збереженням метаданих та відновленням підпису з бази — 4–6 днів.







