Build SVGator

KINDAreplaces $24/mosaves $288/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A browser SVG animation editor whose hard problems are a preview that cannot disagree with the export, transform origins that every engine interprets the same way, and a project file that outlives the editor, rather than the timeline widgets people notice first.

estimated effort multi-daythe files for this build are in the project pack

RuntimeVite, React and TypeScript, entirely in the browser with no backendDocumentThe original SVG kept as a live DOM, never re-serialised from a modelAnimationCSS keyframes generated from the timeline and injected for previewOriginstransform-box and transform-origin set explicitly on every animated elementStorageVersioned project files in IndexedDB and exported to disk

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the dev server, the build and the test suite.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Transform origins and animation timing differ between engines, and an export checked in one engine is not a checked export.

    Get it Install the browsers your test runner supports and run the engine suite in all three.

    Verify The engine suite runs in three engines and passes

  3. have readyfree

    Why Files from real vector editors contain defs, nested groups, clip paths and elements with no ids, and the round trip is only meaningful against those.

    Get it Export a dozen files from the editors you actually use, including ones with gradients, masks and text.

    Verify Every file loads and round trips unchanged

  4. have readyfree

    Why The SVG defaults are not what anyone expects, and the export depends on writing both properties on every animated element.

    Get it Read the two property definitions and note the initial values and the SVG rule. open ↗

    Verify A note records the initial values and why the export always sets both

  5. decidefree

    Why It is baked into every export and cannot change later without changing every existing animation.

    Get it Decide translate, rotate, scale or another order, and write it into the interface.

    Verify The order is documented and asserted in the suite

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- projects(id, name, format_version, svg_source, duration_ms, created_at, updated_at) · the source is kept verbatim
- elements(project_id, element_id, tag, label, assigned_id) · assigned_id is set only where the file had none
- tracks(id, project_id, element_id, property) · property is translateX, translateY, rotate, scale or opacity
- keyframes(id, track_id, at_ms, value, easing) · easing belongs to the segment leaving this keyframe
- origins(project_id, element_id, box, origin_x, origin_y) · written into the export on every animated element
- exports(id, project_id, kind, at, bytes, engines_checked_json) · kind is animated_svg or css_only
- The SVG source is stored as text and the DOM is rebuilt from it on load, so nothing the editor did not understand is ever lost between sessions.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
PROJECT_FORMAT_VERSIONrequired1Written into every project file. Migrations read it.
DEFAULT_DURATION_MSrequired3000New project duration. Editable per project.
TRANSFORM_ORDERrequiredtranslate,rotate,scaleFixed and stated in the interface. Changing it changes every animation.
DEFAULT_TRANSFORM_BOXrequiredfill-boxEmitted on every animated element with an explicit origin.
UNDO_DEPTHrequired50Fast keyframe work produces mistakes and they should be cheap.
REQUEST_PERSISTENT_STORAGErequiredtrueAsked for at first save. Even when granted, the exported file is the real save.

The build, in order

  1. Loading a file without changing it

    The editor understands the file's structure and does not alter what it does not touch.

    1. A model of your own drops whatever it did not have a slot for.

      Files src/document.ts

    2. Only where missing, and unique against what the file already has.

      Files src/ids.tssrc/ids.test.ts

    3. Semantic equality of the two DOMs, not string equality.

      Files tests/roundtrip.test.ts

    done when · tick each as it passes
    watch out
    • Building an internal model of shapes and re-serialising it for export drops every attribute, namespace and element the model did not have a slot for, and the user finds out when a gradient or a clip path disappears. Keep the original DOM as the document and only add attributes to it.
  2. A preview that runs the export

    Scrubbing the timeline shows exactly what the exported file will show.

    1. One generator, used for preview and export alike.

      Files src/css/keyframes.tssrc/css/keyframes.test.ts

    2. The browser does the interpolation, which is the point.

      Files src/preview.ts

    3. Computed transforms at three times, not a visual glance.

      Files tests/preview-equals-export.test.ts

    done when · tick each as it passes
    watch out
    • Interpolating in JavaScript for the preview while emitting CSS for the export produces two implementations that agree on the easy cases and diverge on easing, rotation past a half turn and overlapping keyframes, and the person only sees the difference after shipping. Make the preview run the generated stylesheet, so there is one implementation.
  3. Origins every engine agrees on

    A rotation spins around the point the user chose, in every browser.

    1. The defaults for SVG are the viewport and its corner.

      Files src/css/origins.tstests/origins-present.test.ts

    2. Translate, rotate, scale, and say so in the interface.

      Files src/css/compose.ts

    3. A rotation about the centre must land in the same place.

      Files tests/engines.spec.ts

      terminal
      npx playwright test tests/engines.spec.ts --project=chromium --project=firefox --project=webkit
    done when · tick each as it passes
    watch out
    • The specification's initial transform-box is the nearest SVG viewport, and for SVG elements without a CSS layout box the used transform-origin is the top-left corner, so a rotation exported without both properties spins the element around the corner of the whole drawing rather than its own centre, and engines have historically differed on top of that. Emit both properties on every animated element and never rely on the default.
  4. A timeline you can work in for an hour

    Keyframes can be placed, moved, copied and undone without fighting the interface.

    1. Zooming the timeline must not move a keyframe.

      Files src/timeline/snap.tssrc/timeline/snap.test.ts

    2. Deleting a track and pasting keyframes both undo cleanly.

      Files src/undo.ts

    3. Smoothness is a number, not an impression.

      Files tests/perf.spec.ts

    done when · tick each as it passes
    watch out
    • Snapping keyframes to pixels rather than to time means the same drag lands on a different millisecond at a different zoom, and the animation subtly changes when the user zooms the timeline. Snap in time units and derive pixels from them.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more design.