[{"data":1,"prerenderedAt":262},["ShallowReactive",2],{"project-ru-image-editor":3,"recommendation-mention-sources":200},{"id":4,"title":5,"body":6,"category":154,"context":154,"cover":155,"coverAlt":156,"demo":157,"description":158,"extension":159,"featured":160,"locale":161,"meta":162,"navigation":163,"order":148,"path":164,"published":163,"repository":165,"results":166,"role":173,"seo":174,"slug":175,"stack":176,"stem":197,"year":198,"__hash__":199},"projects\u002Fru\u002Fprojects\u002Fimage-editor.md","Open-source ядро браузерного редактора изображений на TypeScript и FabricJS",{"type":7,"value":8,"toc":139},"minimark",[9,14,18,37,41,44,48,53,72,75,79,82,85,92,96,99,102,106,109,112,116,119,122,126],[10,11,13],"h2",{"id":12},"задача","Задача",[15,16,17],"p",{},"В марте 2025 года inSales понадобился браузерный редактор для фото товаров. Проще всего было написать работу с canvas прямо во Vue-экране, где редактор будет жить. Я решил сделать отдельную библиотеку на TypeScript поверх FabricJS: canvas, объекты, история и правила редактирования — это самостоятельная система, и внутри одного продуктового компонента её сложно тестировать и нельзя переиспользовать.",[15,19,20,21,31,32,36],{},"Библиотека опубликована как ",[22,23,27],"a",{"href":24,"rel":25},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@anu3ev\u002Ffabric-image-editor",[26],"nofollow",[28,29,30],"code",{},"@anu3ev\u002Ffabric-image-editor"," под лицензией MIT и работает движком ",[22,33,35],{"href":34},"\u002Fru\u002Fwork\u002Fproduct-image-editor\u002F","редактора товарной инфографики inSales",". Про продукт, его пользователей и AI-функции — в том кейсе. Здесь — про сам движок.",[10,38,40],{"id":39},"моя-роль","Моя роль",[15,42,43],{},"Библиотеку сделал один. Спроектировал архитектуру и публичный API, написал код и тесты, настроил сборку, CI и выпуск версий в GitHub Releases, npm и демо на GitHub Pages. Подтверждённый production-потребитель один — моя же Vue-интеграция в inSales. После ухода из inSales в сентябре 2026 года продолжаю поддерживать библиотеку.",[10,45,47],{"id":46},"что-я-сделал","Что я сделал",[49,50,52],"h3",{"id":51},"менеджеры-за-публичным-api","Менеджеры за публичным API",[15,54,55,56,59,60,63,64,67,68,71],{},"Редактор создаёт рабочую область внутри контейнера, который передаёт приложение. Обязанности разделены между менеджерами. ",[28,57,58],{},"CanvasManager"," отвечает за размеры canvas, рабочую область (montage area) и viewport, ",[28,61,62],{},"HistoryManager"," сохраняет и восстанавливает состояние, ",[28,65,66],{},"ShapeManager"," синхронизирует фигуру с текстом внутри неё, ",[28,69,70],{},"CropManager"," держит временное состояние обрезки. Есть отдельные менеджеры для изображений, текста, шаблонов и привязок.",[15,73,74],{},"Внешнее приложение работает только через публичные методы, настройки и события и не трогает FabricJS-объекты напрямую. Продуктовые правила подключаются через точки расширения: можно запретить удалять конкретный объект или чистить его служебные данные при копировании, а асинхронный хук импорта позволяет приложению сначала загрузить файл к себе и вернуть готовый источник. AI в библиотеку я намеренно не включал: приложение само вызывает свой сервис и передаёт картинку или текст через тот же API.",[49,76,78],{"id":77},"одна-сериализация-на-все-пути-появления-объекта","Одна сериализация на все пути появления объекта",[15,80,81],{},"Объект можно создать, импортировать, вставить из буфера, сгруппировать, восстановить из сохранённого проекта, получить из шаблона или вернуть через undo. На каждом пути нужно восстановить и свойства Fabric, и данные библиотеки: идентификаторы, блокировку, возможность выделения, стили rich text, связи вложенных объектов.",[15,83,84],{},"Поэтому один механизм сериализации обслуживает сохранённые проекты, историю, шаблоны и буфер обмена. Временные состояния в него не попадают: активный crop существует только во время взаимодействия и уходит в историю, когда пользователь применил результат. Операции, которые меняют сразу несколько свойств, записываются в историю одним шагом, чтобы одно действие отменялось одним undo.",[15,86,87],{},[88,89],"img",{"alt":90,"src":91},"Режим обрезки рабочей области в публичном демо: сетка поверх композиции и кнопки Apply Crop и Cancel Crop","\u002Fimages\u002Fwork\u002Fimage-editor\u002Fdemo-canvas-crop-mode.webp",[49,93,95],{"id":94},"геометрия-в-четырёх-системах-координат","Геометрия в четырёх системах координат",[15,97,98],{},"Координаты canvas, viewport с zoom и pan, рабочая область и локальные координаты групп — разные пространства. Если их путать, объекты прыгают при resize, crop или восстановлении. Преобразования между ними — зона библиотеки. При изменении размера canvas или загрузке проекта она сдвигает viewport, а объекты остаются на месте, и погрешность не копится в сохранённой композиции.",[15,100,101],{},"Шаблоны хранят позиции относительно рабочей области с привязкой к началу, центру или концу по каждой оси, поэтому раскладка сохраняется и на области другого размера. Привязки к краям, центрам и равным интервалам, а также измерение расстояний с зажатым Alt считаются прямо во время перетаскивания с учётом zoom и трансформаций.",[49,103,105],{"id":104},"убрал-base64-из-состояния-редактора","Убрал base64 из состояния редактора",[15,107,108],{},"В ранних версиях большие изображения хранились в объектах и истории как base64. Каждое сериализованное состояние раздувалось, а тяжёлая работа шла в основном потоке. Я перевёл ресурсы на Blob и Object URL, добавил кеш с явной очисткой при замене картинки и уничтожении редактора, а тяжёлый импорт и экспорт вынес в Web Workers. Изображения стали загружаться больше чем в два раза быстрее, а долгие операции перестали подвешивать интерфейс.",[15,110,111],{},"Экспорт рендерится из отдельной копии canvas, поэтому zoom, выделение и viewport на экране не меняются. Учитываются device pixel ratio и масштаб браузера: размер итогового файла не зависит от настроек экрана.",[49,113,115],{"id":114},"тесты-проходят-через-само-взаимодействие","Тесты проходят через само взаимодействие",[15,117,118],{},"Логику покрывает Jest, а Playwright гоняет браузерные сценарии для crop, выделения, масштабирования, привязок и редактирования текста. Часть багов видна только посреди перетаскивания, поэтому тесты проверяют и промежуточные состояния.",[15,120,121],{},"В октябре 2026 года я переделал релиз и инициализацию. Теперь релизный workflow прогоняет typecheck, lint, Jest и браузерные тесты до публикации в npm, а тег и GitHub Release создаёт отдельный job, который можно перезапустить без повторной публикации. Инициализация дожидается готовности, сообщает об ошибках и останавливает устаревшие асинхронные операции.",[10,123,125],{"id":124},"результат","Результат",[127,128,129,133,136],"ul",{},[130,131,132],"li",{},"Изображения загружаются больше чем в два раза быстрее после перехода с base64 на Blob\u002FObject URL, кеша и Web Workers. Речь именно о загрузке изображений, не обо всех операциях.",[130,134,135],{},"Одну композицию можно экспортировать в PNG, JPEG, SVG или PDF.",[130,137,138],{},"Публичные показатели на 21 октября 2026 года: 19 звёзд и 7 форков на GitHub, 188 скачиваний из npm за неделю 12–18 октября. Скачивания — не число пользователей.",{"title":140,"searchDepth":141,"depth":141,"links":142},"",2,[143,144,145,153],{"id":12,"depth":141,"text":13},{"id":39,"depth":141,"text":40},{"id":46,"depth":141,"text":47,"children":146},[147,149,150,151,152],{"id":51,"depth":148,"text":52},3,{"id":77,"depth":148,"text":78},{"id":94,"depth":148,"text":95},{"id":104,"depth":148,"text":105},{"id":114,"depth":148,"text":115},{"id":124,"depth":141,"text":125},"Open source","\u002Fimages\u002Fwork\u002Fimage-editor\u002Fdemo-sample-composition.webp","Публичное демо редактора с примером композиции, выделенным заголовком и данными объекта в панели Canvas State","https:\u002F\u002Fanu3ev.github.io\u002Fimage-editor\u002F","Библиотека редактора изображений с публичным API, полной сериализацией состояния, шаблонами, привязками и экспортом в четыре формата. После переделки хранения картинок изображения стали загружаться больше чем в 2 раза быстрее.","md",false,"ru",{},true,"\u002Fru\u002Fprojects\u002Fimage-editor","https:\u002F\u002Fgithub.com\u002FAnu3ev\u002Fimage-editor",[167,170],{"value":168,"label":169},">2×","быстрее загружаются изображения после перехода с base64 на Blob и Object URL",{"value":171,"label":172},"4","формата экспорта одной композиции: PNG, JPEG, SVG и PDF","Единственный автор и мейнтейнер — от архитектуры и публичного API до тестов и релизов",{"title":5,"description":158},"image-editor",[177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],"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\u002FCD","Git","ru\u002Fprojects\u002Fimage-editor","Март 2025 – сейчас","9vguxrnEJW4jV8OnBjT-ra2NUFLfL70cDTlirLKppwY",[201,212,218,232,242,247,252,257],{"stem":202,"name":203,"date":204,"featured":141,"published":163,"mentions":205},"recommendations\u002Fandrey-stikheev","Andrey Stikheev","2026-09-08",[206,209],{"project":207,"quote":208},"product-image-editor","Among his most notable projects was a sophisticated browser-based image editor built with TypeScript and Fabric.js, now used by thousands of merchants.",{"project":210,"quote":211},"abandoned-carts","He also developed the frontend for a paid abandoned-cart product and contributed to the production migration from Vue 2 to Vue 3.",{"stem":213,"name":214,"date":215,"featured":216,"published":163,"mentions":217},"recommendations\u002Fdaniil-churakov","Daniil Churakov","2026-08-27",null,[],{"stem":219,"name":220,"date":221,"featured":148,"published":163,"mentions":222},"recommendations\u002Fdmitry-sadovsky","Dmitry Sadovsky","2026-08-22",[223,226,228,230],{"project":224,"quote":225},"visual-storefront","For years he was the driving force behind our visual storefront editor — a large, complex application where merchants build their online stores.",{"project":207,"quote":227},"It became the tool merchants use to design product images right inside the admin panel — the kind of component companies usually buy rather than build.",{"project":175,"quote":229},"What impressed me most was the infographic editor he built essentially from scratch: a full canvas-based graphic editor with artboards, shapes, text styling, templates, and undo\u002Fredo history, powered by an editor core he developed himself.",{"project":210,"quote":231},"Our abandoned carts functionality — with multi-channel customer messaging, communication history, and analytics — was largely his work, from UI down to the details of how requests are cancelled and states stay in sync.",{"stem":233,"name":234,"date":235,"featured":236,"published":163,"mentions":237},"recommendations\u002Fegor-fedkin","Egor Fedkin","2026-08-31",1,[238,240],{"project":207,"quote":239},"Today, the tool is actively used by around 3,000 users.",{"project":210,"quote":241},"It has already generated approximately twice the revenue needed to cover its development costs.",{"stem":243,"name":244,"date":245,"featured":216,"published":163,"mentions":246},"recommendations\u002Femmanuel-stephen","Emmanuel Stephen","2026-10-11",[],{"stem":248,"name":249,"date":250,"featured":216,"published":163,"mentions":251},"recommendations\u002Fevgeny-lonshakov","Евгений Лоншаков","2026-09-21",[],{"stem":253,"name":254,"date":255,"featured":216,"published":163,"mentions":256},"recommendations\u002Froman-gorbachev","Роман Горбачев","2026-09-17",[],{"stem":258,"name":259,"date":260,"featured":216,"published":163,"mentions":261},"recommendations\u002Fvladimir-bedarev","Bedarev Vladimir","2026-09-16",[],1793195987378]