[{"data":1,"prerenderedAt":262},["ShallowReactive",2],{"project-en-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\u002Fen\u002Fprojects\u002Fimage-editor.md","An open-source image editor core in TypeScript and 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},"the-problem","The problem",[15,16,17],"p",{},"In March 2025 inSales needed a browser editor for product images. The quick path was to put the canvas code inside the Vue screen that would use it. I chose to build it as a separate TypeScript library on top of FabricJS instead: canvas, objects, history and editing rules form a system of their own, and inside one product component they would be hard to test and impossible to reuse.",[15,19,20,21,31,32,36],{},"The library is published as ",[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"," under the MIT license and is the engine of the ",[22,33,35],{"href":34},"\u002Fwork\u002Fproduct-image-editor\u002F","inSales product image editor",". That case covers the product, its users and its AI workflows. This one covers the engine.",[10,38,40],{"id":39},"my-role","My role",[15,42,43],{},"I built the library alone. I designed the architecture and the public API, wrote the code and tests, and set up the build, CI and releases to GitHub Releases, npm and the GitHub Pages demo. The only production consumer I can confirm is my own Vue integration at inSales. I keep maintaining the library after leaving inSales in September 2026.",[10,45,47],{"id":46},"what-i-built","What I built",[49,50,52],"h3",{"id":51},"managers-behind-a-public-api","Managers behind a public API",[15,54,55,56,59,60,63,64,67,68,71],{},"The editor creates a workspace inside a container the host passes in. Responsibilities are split between managers: ",[28,57,58],{},"CanvasManager"," owns canvas size, the montage area and the viewport, ",[28,61,62],{},"HistoryManager"," stores and restores state, ",[28,65,66],{},"ShapeManager"," keeps a shape and its inner text in sync, ",[28,69,70],{},"CropManager"," holds the temporary crop state, and others for images, text, templates and snapping.",[15,73,74],{},"A host application works only through public methods, options and events. It never mutates FabricJS objects directly. Product rules plug in through extension points: the host can forbid deleting a specific object or strip its service data when it is copied, and an async import hook lets the host upload a file first and hand back the prepared source. AI is deliberately out of scope. The host calls its own service and passes the image or text in through the same API.",[49,76,78],{"id":77},"one-serialization-for-every-way-an-object-appears","One serialization for every way an object appears",[15,80,81],{},"An object can be created, imported, pasted, grouped, restored from a saved project, applied from a template or brought back by undo. Each path has to restore Fabric's own properties and the library's data: IDs, locks, selectability, rich-text styles and links between nested objects.",[15,83,84],{},"So I made one serialization mechanism serve saved projects, history, templates and the clipboard. Temporary states stay out of it. An active crop lives only during the interaction and reaches history once the user applies it. Operations that change several properties at once are committed as a single history entry, so one user action needs one undo.",[15,86,87],{},[88,89],"img",{"alt":90,"src":91},"Canvas crop mode in the public demo: a grid over the composition and Apply Crop and Cancel Crop controls","\u002Fimages\u002Fwork\u002Fimage-editor\u002Fdemo-canvas-crop-mode.webp",[49,93,95],{"id":94},"geometry-in-four-coordinate-spaces","Geometry in four coordinate spaces",[15,97,98],{},"Canvas coordinates, the zoomed and panned viewport, the montage area and local group coordinates are different spaces. Mixing them makes objects jump on resize, crop or restore. The library owns those conversions. When the canvas is resized or a project is restored, it moves the viewport instead of every object, so offsets do not accumulate in the saved composition.",[15,100,101],{},"Templates store positions relative to the montage area with start, center or end anchors on each axis, so a template keeps its layout on a workspace of a different size. Snapping to edges, centers and equal spacing, and distance measurement on Alt, are computed during the drag with zoom and transforms taken into account.",[49,103,105],{"id":104},"taking-base64-out-of-editor-state","Taking base64 out of editor state",[15,107,108],{},"Early versions kept large images as base64 inside objects and history. That bloated every serialized state and put heavy work on the main thread. I replaced it with Blob and Object URLs, added a resource cache with explicit cleanup when an image is replaced or the editor is destroyed, and moved heavy import and export work to Web Workers. Image loading became more than twice as fast, and long operations stopped freezing the interface.",[15,110,111],{},"Export renders from a separate copy of the canvas, so the zoom, selection and viewport on screen stay untouched. It also accounts for device pixel ratio and browser zoom, so output size does not depend on the user's display settings.",[49,113,115],{"id":114},"tests-that-follow-the-interaction","Tests that follow the interaction",[15,117,118],{},"Jest covers the logic, and Playwright runs browser scenarios for crop, selection, scaling, snapping and text editing. Some bugs only appear mid-drag, so tests also check intermediate states.",[15,120,121],{},"In October 2026 I reworked the release and initialization paths. The release workflow now runs typecheck, lint, Jest and browser tests before npm publication, and creates the tag and GitHub Release in a separate job that can be retried without republishing. Initialization now waits for readiness, reports failures and stops stale async work.",[10,123,125],{"id":124},"results","Results",[127,128,129,133,136],"ul",{},[130,131,132],"li",{},"Image loading is more than twice as fast after the move from base64 to Blob\u002FObject URLs, caching and Web Workers. This is about image loading, not every operation.",[130,134,135],{},"One composition exports to PNG, JPEG, SVG or PDF.",[130,137,138],{},"Public signals, checked on October 21, 2026: 19 GitHub stars, 7 forks and 188 npm downloads in the week of October 12–18. Downloads are not a count of users.",{"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","The public image editor demo with a sample composition, a selected heading and its object data in the Canvas State panel","https:\u002F\u002Fanu3ev.github.io\u002Fimage-editor\u002F","A browser image editor library with a public API, full state serialization, templates, snapping and export to four formats. Image loading became more than 2× faster after I reworked how image data is stored.","md",false,"en",{},true,"\u002Fen\u002Fprojects\u002Fimage-editor","https:\u002F\u002Fgithub.com\u002FAnu3ev\u002Fimage-editor",[167,170],{"value":168,"label":169},">2×","faster image loading after replacing base64 image data with Blob and Object URLs",{"value":171,"label":172},"4","export formats from one composition, PNG, JPEG, SVG and PDF","Sole author and maintainer, from architecture and public API to tests and releases",{"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","en\u002Fprojects\u002Fimage-editor","Mar 2025 – present","NlPoORTA9bVFvZUZj0rU2uXh-xbXfWYVRe0ik7uAm1g",[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",[],1793195987939]