Реализация электронной подписи (рисование/ввод) на сайте
Разработчики сталкиваются с типичной проблемой: пользователь на мобильном устройстве пытается нарисовать подпись пальцем, а 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, hash документа. Это обеспечивает базовый аудит-след. Дополнительно можно добавить невидимый водяной знак с метаданными — он не ухудшает читаемость, но позволяет восстановить владельца при редактировании файла.
Почему touch-none критичен для мобильных устройств?
Без touch-none браузер интерпретирует касание как прокрутку страницы, а не рисование на canvas. Это приводит к задержкам и ложным срабатываниям. Мы устанавливаем CSS-свойство touch-action: none на canvas, что даёт приоритет рисованию. На мобильных устройствах задержка снижается с 300 мс до 16 мс — разница заметна пользователю.
Как обеспечить юридическую значимость подписи?
Визуальная подпись не имеет юридической силы без дополнительных мер. Мы фиксируем время, IP, user-agent и хэш документа в момент подписания. Если нужна полная юридическая значимость, добавляем интеграцию с КЭП (квалифицированная электронная подпись) — это увеличивает сроки до 5 дней, но даёт соответствие 63-ФЗ.
Процесс работы
- Аналитика — определяем требования к подписи (юридическая сила, количество подписантов).
- Проектирование — выбираем методы ввода, дизайн виджетов.
- Реализация — пишем компоненты на 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 часов.







