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

Ваши пользователи рисуют, делают пометки и аннотации прямо на сайте, но редактор тормозит, а экспорт искажает результат? Мы разрабатываем canvas-инструменты с нуля или улучшаем существующие, используя проверенные подходы и библиотеки. Наша команда реализует проект под ключ — от выбора архитектуры до внедрения и последующей поддержки, обеспечивая плавную работу и точный экспорт.

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

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

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

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

Однажды к нам пришёл стартап, который хотел внедрить аннотирование 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 — парадигма графического программирования.

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

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

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060