# Open-source ядро браузерного редактора изображений на TypeScript и FabricJS

> Библиотека редактора изображений с публичным API, полной сериализацией состояния, шаблонами, привязками и экспортом в четыре формата. После переделки хранения картинок изображения стали загружаться больше чем в 2 раза быстрее.

- Адрес: https://anu3ev.com/ru/work/image-editor/
- Период: Март 2025 – сейчас
- Где: Open source
- Роль: Единственный автор и мейнтейнер — от архитектуры и публичного API до тестов и релизов
- Навыки: TypeScript, FabricJS, HTML Canvas, JavaScript, Vite, Technical Documentation, Playwright, Problem Solving, E2E, Web Workers, Web Performance, State Management, Front-End Development, AI-Assisted Software Development, Jest, npm, Frontend Architecture, End-to-end Testing, CI/CD, Git
- Результаты:
  - >2×: быстрее загружаются изображения после перехода с base64 на Blob и Object URL
  - 4: формата экспорта одной композиции: PNG, JPEG, SVG и PDF

## Задача

В марте 2025 года inSales понадобился браузерный редактор для фото товаров. Проще всего было написать работу с canvas прямо во Vue-экране, где редактор будет жить. Я решил сделать отдельную библиотеку на TypeScript поверх FabricJS: canvas, объекты, история и правила редактирования — это самостоятельная система, и внутри одного продуктового компонента её сложно тестировать и нельзя переиспользовать.

Библиотека опубликована как [`@anu3ev/fabric-image-editor`](https://www.npmjs.com/package/@anu3ev/fabric-image-editor) под лицензией MIT и работает движком [редактора товарной инфографики inSales](https://anu3ev.com/ru/work/product-image-editor/). Про продукт, его пользователей и AI-функции — в том кейсе. Здесь — про сам движок.

## Моя роль

Библиотеку сделал один. Спроектировал архитектуру и публичный API, написал код и тесты, настроил сборку, CI и выпуск версий в GitHub Releases, npm и демо на GitHub Pages. Подтверждённый production-потребитель один — моя же Vue-интеграция в inSales. После ухода из inSales в сентябре 2026 года продолжаю поддерживать библиотеку.

## Что я сделал

### Менеджеры за публичным API

Редактор создаёт рабочую область внутри контейнера, который передаёт приложение. Обязанности разделены между менеджерами. `CanvasManager` отвечает за размеры canvas, рабочую область (montage area) и viewport, `HistoryManager` сохраняет и восстанавливает состояние, `ShapeManager` синхронизирует фигуру с текстом внутри неё, `CropManager` держит временное состояние обрезки. Есть отдельные менеджеры для изображений, текста, шаблонов и привязок.

Внешнее приложение работает только через публичные методы, настройки и события и не трогает FabricJS-объекты напрямую. Продуктовые правила подключаются через точки расширения: можно запретить удалять конкретный объект или чистить его служебные данные при копировании, а асинхронный хук импорта позволяет приложению сначала загрузить файл к себе и вернуть готовый источник. AI в библиотеку я намеренно не включал: приложение само вызывает свой сервис и передаёт картинку или текст через тот же API.

### Одна сериализация на все пути появления объекта

Объект можно создать, импортировать, вставить из буфера, сгруппировать, восстановить из сохранённого проекта, получить из шаблона или вернуть через undo. На каждом пути нужно восстановить и свойства Fabric, и данные библиотеки: идентификаторы, блокировку, возможность выделения, стили rich text, связи вложенных объектов.

Поэтому один механизм сериализации обслуживает сохранённые проекты, историю, шаблоны и буфер обмена. Временные состояния в него не попадают: активный crop существует только во время взаимодействия и уходит в историю, когда пользователь применил результат. Операции, которые меняют сразу несколько свойств, записываются в историю одним шагом, чтобы одно действие отменялось одним undo.

![Режим обрезки рабочей области в публичном демо: сетка поверх композиции и кнопки Apply Crop и Cancel Crop](https://anu3ev.com/images/work/image-editor/demo-canvas-crop-mode.webp)

### Геометрия в четырёх системах координат

Координаты canvas, viewport с zoom и pan, рабочая область и локальные координаты групп — разные пространства. Если их путать, объекты прыгают при resize, crop или восстановлении. Преобразования между ними — зона библиотеки. При изменении размера canvas или загрузке проекта она сдвигает viewport, а объекты остаются на месте, и погрешность не копится в сохранённой композиции.

Шаблоны хранят позиции относительно рабочей области с привязкой к началу, центру или концу по каждой оси, поэтому раскладка сохраняется и на области другого размера. Привязки к краям, центрам и равным интервалам, а также измерение расстояний с зажатым Alt считаются прямо во время перетаскивания с учётом zoom и трансформаций.

### Убрал base64 из состояния редактора

В ранних версиях большие изображения хранились в объектах и истории как base64. Каждое сериализованное состояние раздувалось, а тяжёлая работа шла в основном потоке. Я перевёл ресурсы на Blob и Object URL, добавил кеш с явной очисткой при замене картинки и уничтожении редактора, а тяжёлый импорт и экспорт вынес в Web Workers. Изображения стали загружаться больше чем в два раза быстрее, а долгие операции перестали подвешивать интерфейс.

Экспорт рендерится из отдельной копии canvas, поэтому zoom, выделение и viewport на экране не меняются. Учитываются device pixel ratio и масштаб браузера: размер итогового файла не зависит от настроек экрана.

### Тесты проходят через само взаимодействие

Логику покрывает Jest, а Playwright гоняет браузерные сценарии для crop, выделения, масштабирования, привязок и редактирования текста. Часть багов видна только посреди перетаскивания, поэтому тесты проверяют и промежуточные состояния.

В октябре 2026 года я переделал релиз и инициализацию. Теперь релизный workflow прогоняет typecheck, lint, Jest и браузерные тесты до публикации в npm, а тег и GitHub Release создаёт отдельный job, который можно перезапустить без повторной публикации. Инициализация дожидается готовности, сообщает об ошибках и останавливает устаревшие асинхронные операции.

## Результат

- Изображения загружаются больше чем в два раза быстрее после перехода с base64 на Blob/Object URL, кеша и Web Workers. Речь именно о загрузке изображений, не обо всех операциях.
- Одну композицию можно экспортировать в PNG, JPEG, SVG или PDF.
- Публичные показатели на 21 октября 2026 года: 19 звёзд и 7 форков на GitHub, 188 скачиваний из npm за неделю 12–18 октября. Скачивания — не число пользователей.
