Every SVG editor I tried forced me into one workflow. Either you edit raw code and squint at a static preview, or you drag shapes around visually and the code is buried somewhere you never touch. I wanted both, so I built SVGDO (svgdo.com) — a browser-based SVG editor where code and canvas stay in lockstep.
In this post, I'll walk through the three hardest technical problems I solved along the way.
Problem 1: Making Code and Canvas Talk to Each Other
Most SVG editors pick a lane. Code-first tools give you a text area and a preview iframe. Visual tools give you a canvas and bury the markup in a sidebar you never open. Bidirectional sync means neither side is the "source of truth" — both are live and either can drive changes.
The core insight is that you need stable element identification across both representations. When the user loads an SVG on svgdo.com, the system parses it with DOMParser and walks the DOM tree, injecting a unique identifier onto every renderable element — paths, rects, circles, groups, text, and so on. Each element also gets recorded in a flat array with its tag name, all its attributes, and an index in the tree walk order.
This gives you a bridge. Click an element on the canvas, read its identifier, find it in the array, and populate the properties panel with its current fill, stroke, and transform values. Modify a property in the panel, update the corresponding attribute on the element in the parsed DOM tree, re-serialize the whole SVG back to code with XMLSerializer, and push the result to the code editor and undo stack. All of this happens synchronously within a single frame, so the user sees the canvas and the code update together with no perceptible lag.
The trickiest part is handling style attributes. SVG elements can carry fill and stroke both as standalone attributes and inside an inline style string. If you update the fill attribute but the element also has fill in its style string, the style wins. So the update logic has to check both places — set the attribute, but also strip it from the style string if it exists there. Otherwise your changes silently don't apply, which makes the editor look broken. I learned this the hard way when testing with SVGs exported from Figma, which heavily uses inline styles.
Problem 2: Dragging Elements With Correct Coordinate Transform
SVG uses its own internal coordinate system. A point that's at screen position (300, 200) might be at SVG coordinate (150, 120) depending on the viewBox, zoom level, and any parent transforms. If you naively map screen pixels to SVG coordinates, your drags will drift — especially when the canvas is zoomed.
The fix is SVGPoint.matrixTransform with the inverse of the element's parent transformation matrix. Every SVG element has a method called getScreenCTM that returns the current transformation matrix mapping the element's local coordinates to screen coordinates. Invert that matrix, multiply it against the mouse's screen position, and you get the exact position in the element's own coordinate space. I spent an embarrassing amount of time debugging drag behavior before realizing the root cause was using the svg element's CTM instead of the parent group's CTM for nested elements.
Once you have the delta between where the user clicked and where they dragged to, you express it as a translate transform on the element. On pointer up, you commit the accumulated transform back to the SVG source code. The key design decision: don't try to modify the element's native x, y, or d attributes. Use the transform attribute as a layer on top. This way, the element's original geometry stays intact, and you can always strip the transform to get back to the starting state — which means undo is trivially just restoring the previous transform value.
Problem 3: SVG Optimization That Actually Saves Space
SVG files exported from Illustrator, Figma, and Sketch contain a shocking amount of bloat. Comments, editor metadata, redundant xmlns declarations, and excessive numeric precision. A path coordinate like 123.456789 is visually identical to 123.457 at any reasonable display scale, but design tools default to maximum precision everywhere.
I built two optimization tiers for svgdo.com. Safe mode strips comments, normalizes whitespace in path data, and removes editor-specific attributes. Aggressive mode goes further: strips all id and data attributes, removes empty group tags that served some purpose during design but are useless in the final file, truncates floating-point precision from six decimal places to three, and collapses all inter-tag whitespace. The precision reduction alone can shave 15 to 20 percent off a typical file.
In testing with real SVGs exported from popular design tools, safe mode typically saves 10 to 30 percent, and aggressive mode can hit 40 to 60 percent. The tradeoff is that aggressive mode removes information you might want if you plan to re-edit the file in a visual tool later. That's why both modes exist and the original code is always kept in memory — one click restores everything. The byte savings are displayed in real time so users can make an informed choice rather than blindly trusting an optimizer.
What I Learned
The browser's built-in SVG tooling is surprisingly powerful. DOMParser and XMLSerializer handle parsing and serialization. SVGPoint and getScreenCTM provide the coordinate math. TreeWalker traverses the DOM efficiently. These are standard APIs that have been in browsers for years, and combined properly they cover 90 percent of what you need for a functional SVG editor.
The remaining 10 percent is UX. Making drag feel natural on a touchpad. Handling edge cases like deeply nested groups where transforms multiply in ways the user doesn't expect. Not breaking the undo stack when the user rapidly toggles an optimization mode. These are the problems you only discover by building something real, putting it online at a real URL, and using it yourself.
SVGDO is live at https://svgdo.com — it includes a 280-plus icon library, keyboard shortcuts for everything, five languages, and multi-format export. Fully open source on GitHub.

Top comments (0)