Canvas-редактор: разработка инструментов рисования и аннотаций

Однажды к нам пришёл стартап, который хотел внедрить аннотирование PDF в веб-приложение. Они использовали SVG-оверлей и на документе в 200 страниц получили лаги — каждый росчерк пера отзывался через секунду. Мы перевели их на Canvas с retained-режимом (Fabric.js) — и скорость отрисовки выросла в 8 р

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Canvas-редактор: разработка инструментов рисования и аннотаций
Сложный
~1-2 недели

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    982
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    995

Однажды к нам пришёл стартап, который хотел внедрить аннотирование PDF в веб-приложение. Они использовали SVG-оверлей и на документе в 200 страниц получили лаги — каждый росчерк пера отзывался через секунду. Мы перевели их на Canvas с retained-режимом (Fabric.js) — и скорость отрисовки выросла в 8 раз, а пользователи перестали жаловаться на тормоза. За более чем 5 лет мы реализовали более 50 canvas-инструментов: от простых кистей до полноценных редакторов диаграмм.

Независимо от сценария — свободное рисование, аннотирование документов или схемы — базовая архитектура сводится к выбору между immediate mode и retained mode. Каждый подход имеет свои компромиссы, и мы поможем вам выбрать правильный под ваши задачи.

Технические сложности при разработке Canvas-инструментов

Свободное рисование с pressure sensitivity. Не все браузеры корректно передают давление пера — приходится эмулировать ширину линии через скорость движения пера. В 80% случаев проблема решается за день: добавляем полифилл для Pointer Events и калибруем алгоритм под конкретные устройства (Windows Ink, iPad, Wacom).

Аннотирование PDF. Координаты аннотаций должны быть привязаны к странице, а не к экрану. При зуммировании и скролле позиции пересчитываются с учётом current scale. Мы используем масштабируемые слои и перехватываем события transform stage в Konva.js.

Валидация экспорта. Аннотации должны точно ложиться на оригинал. Мы сначала рендерим PDF как изображение, затем поверх накладываем canvas с аннотациями — так исключаются расхождения в DPI.

Почему retained mode предпочтительнее для аннотирования?

Immediate mode (чистый Canvas 2D) быстр, но требует ручного hit-testing и управления сценами. Retained mode (Fabric.js, Konva.js) предоставляет объектную модель: каждая фигура — объект, который можно выделить, переместить, изменить. Для редакторов с undo/redo и экспортом retained mode сокращает время разработки вдвое. Кроме того, retained mode удобнее для командной работы: изменения в объектной модели легко синхронизировать через WebSocket — это позволяет реализовать совместное рисование онлайн.

Характеристика Immediate mode Retained mode
Производительность Высокая (низкий оверхед) Средняя (зависит от числа объектов)
Объектная модель Нет, пишется вручную Встроенная (фигуры, группы)
Undo/Redo Громоздкий (строки пикселей) Простой (снимки объектов)
Hit-testing Координатная математика Встроенный (на основе bounding box)
Подходит для Freehand, кисти, эластичные линии Фигуры, текст, выделение объектов

Как мы реализуем аннотирование с Konva.js

Мы используем Konva.js 9 с React-Konva. Каждая аннотация — объект класса Rect, Line, Circle, Text или Arrow. Пользователь выбирает инструмент, рисует на stage — объект добавляется в массив и рендерится. Выделение, перемещение и редактирование встроенные.

import { Stage, Layer, Line, Rect, Circle, Text, Transformer } from 'react-konva'; import Konva from 'konva'; type AnnotationType = 'line' | 'rect' | 'circle' | 'arrow' | 'text'; interface Annotation { id: string; type: AnnotationType; points?: number[]; x?: number; y?: number; width?: number; height?: number; text?: string; color: string; } function AnnotationTool({ backgroundImage }: { backgroundImage: string }) { const [annotations, setAnnotations] = useState<Annotation[]>([]); const [selectedId, setSelectedId] = useState<string | null>(null); const [activeTool, setActiveTool] = useState<AnnotationType>('rect'); const [isDrawing, setIsDrawing] = useState(false); const stageRef = useRef<Konva.Stage>(null); function getRelativePosition() { const stage = stageRef.current!; const pos = stage.getPointerPosition()!; return { x: pos.x, y: pos.y }; } function handleMouseDown() { setSelectedId(null); const pos = getRelativePosition(); const newAnnotation: Annotation = { id: crypto.randomUUID(), type: activeTool, color: '#ef4444', x: pos.x, y: pos.y, width: 0, height: 0, }; if (activeTool === 'line') { newAnnotation.points = [pos.x, pos.y, pos.x, pos.y]; } setAnnotations((prev) => [...prev, newAnnotation]); setIsDrawing(true); } function handleMouseMove() { if (!isDrawing) return; const pos = getRelativePosition(); const lastIndex = annotations.length - 1; const last = annotations[lastIndex]; const updated = { ...last }; if (activeTool === 'line') { updated.points = [last.points![0], last.points![1], pos.x, pos.y]; } else { updated.width = pos.x - last.x!; updated.height = pos.y - last.y!; } setAnnotations((prev) => [...prev.slice(0, lastIndex), updated]); } function handleMouseUp() { setIsDrawing(false); } return ( <Stage ref={stageRef} width={800} height={600} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > <Layer> {annotations.map((ann) => { if (ann.type === 'rect') { return ( <Rect key={ann.id} x={ann.x} y={ann.y} width={ann.width} height={ann.height} stroke={ann.color} strokeWidth={2} fill="transparent" onClick={() => setSelectedId(ann.id)} draggable={selectedId === ann.id} /> ); } if (ann.type === 'line') { return ( <Line key={ann.id} points={ann.points} stroke={ann.color} strokeWidth={2} lineCap="round" /> ); } return null; })} </Layer> </Stage> ); } 

Как реализовать Undo/Redo в Canvas-редакторе?

Для отмены и повтора действий мы используем стек состояний. Каждая операция создаёт снимок текущих аннотаций и помещает его в историю. Управление через undo и redo с поддержкой ветвления — при новом действии после отмены история обрезается.

function useUndoRedo<T>(initialState: T) { const [history, setHistory] = useState<T[]>([initialState]); const [cursor, setCursor] = useState(0); const current = history[cursor]; function push(newState: T) { // Обрезаем историю после текущей позиции (ветвление) const newHistory = [...history.slice(0, cursor + 1), newState]; setHistory(newHistory); setCursor(newHistory.length - 1); } function undo() { if (cursor > 0) setCursor((c) => c - 1); } function redo() { if (cursor < history.length - 1) setCursor((c) => c + 1); } return { current, push, undo, redo, canUndo: cursor > 0, canRedo: cursor < history.length - 1 }; } 

Процесс разработки Canvas-инструмента

Этап Описание Срок
Аналитика Изучаем сценарии: freehand, аннотирование, диаграммы. Определяем формат сериализации (JSON, SVG). 1 день
Проектирование Выбираем стек (Canvas 2D, Konva, Fabric). Проектируем объектную модель и структуру слоёв. 1–2 дня
Реализация Разрабатываем инструменты (кисть, фигуры, текст). Интегрируем undo/redo, экспорт, поддержку pointer. 3–5 дней
Тестирование Проверяем на разных устройствах (iPad, Windows, Android). Исправляем баги с тач-событиями и давлением. 1–2 дня
Деплой Документируем API, настраиваем сборку, обучаем команду заказчика. 1 день

Как мы экспортируем аннотированное изображение

Фон (PDF или изображение) рендерится первым слоем, аннотации — вторым. Затем canvas одного stage конвертируется в Blob через toBlob(). Учитываем HiDPI: умножаем размеры на window.devicePixelRatio.

function exportAnnotated(stage: Konva.Stage, bgImage: HTMLImageElement) { const canvas = document.createElement('canvas'); canvas.width = stage.width(); canvas.height = stage.height(); const ctx = canvas.getContext('2d')!; // Сначала фон ctx.drawImage(bgImage, 0, 0); // Поверх — аннотации из Konva const stageCanvas = stage.toCanvas(); ctx.drawImage(stageCanvas, 0, 0); canvas.toBlob((blob) => { const url = URL.createObjectURL(blob!); const a = document.createElement('a'); a.href = url; a.download = 'annotated.png'; a.click(); URL.revokeObjectURL(url); }); } 
Чек-лист типичных ошибок при разработке Canvas-инструментов
  • touchAction: none не выставлен — тач-события перехватываются браузером.
  • Координаты не масштабируются при изменении размера окна — используем resize handler с пересчётом масштаба.
  • Undo/Redo хранит полные стейты (фулл-снэпшоты) — для больших документов объём памяти может расти; в production используем команды (Command pattern) с замыканиями.
  • Экспорт в PNG без учёта devicePixelRatio — изображение размыто на Retina.
  • Не обработано событие pointercancel — при прерывании жеста остаётся артефакт.

Сроки и что входит в работу

Сроки: базовый инструмент с кистью и фигурами — от 3 до 5 дней. Аннотирование PDF с сохранением координат — от 7 до 10 дней. Стоимость рассчитывается индивидуально после анализа вашего ТЗ. За счёт оптимизации архитектуры вы можете сэкономить до 40% времени на разработку.

Отметим: что входит:

  • Исходный код с комментариями
  • Документация по API (основные методы и события)
  • Инструкция по развёртыванию
  • 2 часа обучения команды заказчика
  • Гарантия 30 дней на исправление багов

Почему выбирают нас: 5+ лет опыта в разработке canvas-решений, 50+ реализованных проектов, сертифицированные разработчики React и Node.js. Обращайтесь к нам за консультацией по архитектуре Canvas — ответим в течение дня.

Для справки: Canvas API — часть WHATWG HTML Living Standard, retained mode — парадигма графического программирования.