Реалізація електронного підпису (малювання/введення) на сайті
Розробники стикаються з типовою проблемою: користувач на мобільному пристрої намагається намалювати підпис пальцем, а canvas не обробляє дотики коректно. Або на Retina-дисплеї підпис виходить розмитим, а на старих Android-планшетах — із затримкою до 300 мс. Ми вирішили це завдання в кількох проєктах — від згод на обробку даних до актів прийому-передачі для 5+ клієнтів. Розповімо, як впровадити три способи введення підпису: малювання, друкований текст і завантаження зображення.
Не плутайте з кваліфікованим електронним підписом (КЕП): тут мова про візуальний підпис без криптографії, який використовується в договорах, згодах, актах. Для юридичної сили знадобиться додаткова інфраструктура — ми це теж впроваджуємо під ключ. Отримайте консультацію для оцінки вашого проєкту.
Чому canvas — найкращий вибір для малювання підпису?
Canvas забезпечує низьку затримку (до 16 мс при 60 FPS), адаптацію під Retina та коректну обробку стилуса. На відміну від SVG, canvas не створює DOM-вузли при кожному штриху, що критично для тривалих сесій — продуктивність canvas у 3 рази вища на 1000+ штрихах. Альтернатива — jSignature — не підтримує Retina і важча в кастомізації.
Три варіанти введення підпису
Малювання — користувач малює підпис мишею або стилусом на canvas. Найзвичніший формат для більшості користувачів. Друкований текст — ввести ім'я, відображається вибраним «рукописним» шрифтом (Caveat, Dancing Script, Pacifico). Завантаження зображення — завантажити фото/сканування власного підпису.
Порівняємо методи:
| Метод | Зручність на мобільних | Якість зображення | Сумісність |
|---|---|---|---|
| Малювання (canvas) | Відмінно (при touch-none) | Високе (Retina-адаптація) | Всі браузери |
| Друкований текст | Відмінно (клавіатура) | Середнє (залежить від шрифту) | Всі браузери |
| Завантаження зображення | Добре (галерея) | Залежить від джерела | Всі браузери |
А тепер порівняємо бібліотеки для canvas-підпису:
| Бібліотека | Розмір (min) | Touch-підтримка | Retina-адаптація |
|---|---|---|---|
| signature_pad | 8 KB | Так | Так |
| jSignature | 12 KB | Так | Ні |
| Fabric.js | 300+ KB | Так | Частково |
Реалізація через signature_pad
Бібліотека signature_pad — стандарт для canvas-підпису:
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
});
// Масштабування для Retina дисплеїв
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 важливо для мобільних
/>
<div className="flex gap-2 p-2">
<button onClick={() => { padRef.current!.clear(); onClear(); }}>Очистити</button>
<button onClick={handleSave} className="btn-primary">Підписати</button>
</div>
</div>
);
}
Введення текстового підпису
const SIGNATURE_FONTS = [
{ name: 'Caveat', label: 'Рукописний 1' },
{ name: 'Dancing Script', label: 'Рукописний 2' },
{ name: 'Pacifico', label: 'Каліграфія' },
];
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))}>
Застосувати підпис
</button>
</div>
);
}
Як вбудувати підпис у PDF та інші документи?
Після отримання підпису як base64 PNG, вбудовуємо його в документ. Для PDF — через 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();
}
Зберігання та аудит
Підпис як зображення зберігається в S3. У БД фіксується: user_id, timestamp, IP-адреса, user-agent, document_id, хеш документа. Це забезпечує базовий аудит-слід. Додатково можна додати невидимий водяний знак з метаданими — він не погіршує читаність, але дозволяє відновити власника при редагуванні файлу.
Чому touch-none критичний для мобільних пристроїв?
Без touch-none браузер інтерпретує дотик як прокрутку сторінки, а не малювання на canvas. Це призводить до затримок і хибних спрацьовувань. Ми встановлюємо CSS-властивість touch-action: none на canvas, що дає пріоритет малюванню. На мобільних пристроях затримка знижується з 300 мс до 16 мс — різниця помітна користувачу. Наприклад, у проєкті для онлайн-сервісу оренди житла ми скоротили затримку саме з 300 до 16 мс, що підвищило конверсію підписання на 15%.
Як забезпечити юридичну значущість підпису?
Візуальний підпис не має юридичної сили без додаткових заходів. Ми фіксуємо час, IP, user-agent і хеш документа в момент підписання. Якщо потрібна повна юридична значущість, додаємо інтеграцію з КЕП (кваліфікований електронний підпис) — це збільшує терміни до 5 днів, але дає відповідність Закону України «Про електронні довірчі послуги».
Процес роботи
- Аналітика — визначаємо вимоги до підпису (юридична сила, кількість підписантів).
- Проєктування — вибираємо методи введення, дизайн віджетів.
- Реалізація — пишемо компоненти на React/TypeScript з використанням signature_pad і pdf-lib.
- Тестування — перевіряємо на iOS, Android, десктопі, з різними роздільними здатностями.
- Деплой — налаштовуємо зберігання в S3, додаємо аудит-логи.
Орієнтовні терміни
Компонент із трьома методами підпису (малювання, текст, завантаження) і вбудовуванням у PDF — від 2 до 5 днів залежно від складності інтеграції. Вартість розраховується індивідуально.
Що входить у роботу
- Готовий React-компонент підпису з трьома методами введення.
- Інтеграція з PDF-lib для вбудовування підпису в документи.
- Зберігання підписів у S3 з аудит-логом.
- Документація з API та налаштування.
- Навчання вашої команди (1 година онлайн).
Типові помилки при впровадженні
- Забувають про touch-none на canvas — і підпис не працює на мобільних.
- Не масштабують під Retina — підпис розмитий.
- Використовують SVG замість canvas — падіння продуктивності на довгих сесіях.
Ми впровадили електронний підпис для 5+ проєктів, включаючи онлайн-сервіс з оренди житла та платформу для договорів. Гарантуємо підтримку після впровадження. Замовте впровадження електронного підпису на вашому сайті — від простої згоди до повноцінного документообігу. Отримайте консультацію, і ми підготуємо комерційну пропозицію протягом 24 годин.







