From 9477bba98629347ad2d26a3fe7e21ddb52086c18 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 20:55:34 +0000 Subject: [PATCH 1/3] Add SVG animation editor with timeline, tweening and SMIL export A keyframe animation studio in a single HTML file: draw rects, ellipses, lines, polygons, text and bezier pen paths, then animate them with a multi-track timeline. Auto-key records tweens when shapes are edited away from existing keyframes, including path morphing of pen geometry. Features per-keyframe cubic-bezier easing, onion skinning, a layers panel, undo/redo, localStorage autosave, SVG import, JSON project save/load and export to a self-contained SMIL-animated SVG with spline easing. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BRc6VWxYVrEgEzzrJ2mai3 --- svg-animation-editor.docs.md | 1 + svg-animation-editor.html | 2126 ++++++++++++++++++++++++++++ tests/test_svg_animation_editor.py | 75 + 3 files changed, 2202 insertions(+) create mode 100644 svg-animation-editor.docs.md create mode 100644 svg-animation-editor.html create mode 100644 tests/test_svg_animation_editor.py diff --git a/svg-animation-editor.docs.md b/svg-animation-editor.docs.md new file mode 100644 index 00000000..4c765b8c --- /dev/null +++ b/svg-animation-editor.docs.md @@ -0,0 +1 @@ +A full keyframe animation studio for SVG that runs entirely in the browser. Draw rectangles, ellipses, lines, polygons, text and bezier paths (with a pen tool), then animate them: move the playhead, adjust any shape and auto-key records a tween automatically — position, size, rotation, colors, opacity and even the path geometry itself morph smoothly between keyframes. A multi-track timeline shows a row of keyframe diamonds per animated property with per-keyframe easing curves, scrubbing, looping playback and onion skinning. Includes a layers panel with lock/hide/reorder/duplicate, full undo/redo, localStorage autosave, import of existing SVG files (shapes and paths become editable and animatable) and project save/load as JSON. Export produces a self-contained SMIL-animated .svg file — native `` elements with spline easing that play in any browser with no JavaScript. Keyboard shortcuts throughout (V/A/P/R/O/L/Y/T for tools, space to play, K to keyframe a whole pose). diff --git a/svg-animation-editor.html b/svg-animation-editor.html new file mode 100644 index 00000000..0bd5715f --- /dev/null +++ b/svg-animation-editor.html @@ -0,0 +1,2126 @@ + + + + + +SVG Animation Editor + + + + +
+

SVG Animation Editor

+
+ + + + + + + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ + + + + + +
+
+ +
+ +
+
+ + + + 0.00 / 4.00s + + + + + +
+
+
+ + +

Export animated SVG

+
+ + + Uses native SMIL <animate> elements — plays in any browser, no JavaScript. +
+
+ +
+ + + + + +
+
+ + + + + + diff --git a/tests/test_svg_animation_editor.py b/tests/test_svg_animation_editor.py new file mode 100644 index 00000000..0ddd64e7 --- /dev/null +++ b/tests/test_svg_animation_editor.py @@ -0,0 +1,75 @@ +import pathlib +from playwright.sync_api import Page, expect + +test_dir = pathlib.Path(__file__).parent.absolute() +root = test_dir.parent.absolute() + + +def test_demo_project_loads(page: Page, unused_port_server): + unused_port_server.start(root) + page.goto( + f"http://127.0.0.1:{unused_port_server.port}/svg-animation-editor.html" + ) + expect(page.locator("h1")).to_contain_text("SVG Animation Editor") + # demo project ships with three layers: Ball, Star, Title + expect(page.locator(".layer")).to_have_count(3) + expect(page.locator("#shapesG ellipse")).to_have_count(1) + expect(page.locator("#shapesG polygon")).to_have_count(1) + expect(page.locator("#shapesG text")).to_have_count(1) + # timeline shows keyframe diamonds for the demo animation + assert page.locator(".kf").count() > 0 + + +def test_draw_rectangle_and_autokey_tween(page: Page, unused_port_server): + unused_port_server.start(root) + page.goto( + f"http://127.0.0.1:{unused_port_server.port}/svg-animation-editor.html" + ) + page.evaluate("localStorage.clear()") + page.reload() + layers_before = page.locator(".layer").count() + + # draw a rectangle + page.click('[data-tool="rect"]') + stage = page.locator("#stage").bounding_box() + page.mouse.move(stage["x"] + 60, stage["y"] + 60) + page.mouse.down() + page.mouse.move(stage["x"] + 200, stage["y"] + 160, steps=4) + page.mouse.up() + expect(page.locator(".layer")).to_have_count(layers_before + 1) + + # scrub to 2s, then drag the rectangle: auto-key should create a tween + ruler = page.locator("#ruler").bounding_box() + page.mouse.click(ruler["x"] + 240, ruler["y"] + 12) + expect(page.locator("#timecode")).to_contain_text("2.00") + page.click('[data-tool="select"]') + page.mouse.move(stage["x"] + 130, stage["y"] + 110) + page.mouse.down() + page.mouse.move(stage["x"] + 400, stage["y"] + 300, steps=6) + page.mouse.up() + keyframed = page.evaluate( + "Object.keys(project.shapes[project.shapes.length - 1].tracks).length" + ) + assert keyframed > 0 + + +def test_export_produces_smil_svg(page: Page, unused_port_server): + unused_port_server.start(root) + page.goto( + f"http://127.0.0.1:{unused_port_server.port}/svg-animation-editor.html" + ) + page.evaluate("localStorage.clear()") + page.reload() + page.click("#exportBtn") + code = page.locator("#exportCode").input_value() + assert code.startswith(" { + const doc = new DOMParser().parseFromString(c, 'image/svg+xml'); + return !doc.querySelector('parsererror'); + }""", + code, + ) + assert parses From b9c3c17ac4f3e4c64d8853d140d4ea797c620225 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 05:44:37 +0000 Subject: [PATCH 2/3] Make SVG animation editor usable on mobile Safari - Use 100dvh so the timeline is not hidden under Safari's browser chrome (body is overflow:hidden, so it was unreachable before) - Stack the layout vertically on small screens: swipeable single-row toolbar and transport, side-by-side properties/layers panel, and a shorter timeline that always stays on screen - Add a "fit" zoom mode (default on small screens) that scales the canvas to the available space; the stage wrapper now uses flex + margin:auto so oversized canvases scroll correctly in both axes - Fix touch interaction: timeline tracks allow touch scrolling (pan-x pan-y) while the ruler and keyframe diamonds remain draggable (touch-action:none), with pointercancel cleanup - Larger touch targets on coarse pointers: keyframe diamonds, selection/rotate handles and path nodes - Add a floating done/cancel bar while drawing pen paths and polygons (no Enter/Escape keys on mobile) - Inset timeline tracks by 8px so t=0 keyframes are not stuck under the sticky label column - maximum-scale=1 viewport stops iOS auto-zoom when focusing inputs Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BRc6VWxYVrEgEzzrJ2mai3 --- svg-animation-editor.html | 120 +++++++++++++++++++++++------ tests/test_svg_animation_editor.py | 3 +- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/svg-animation-editor.html b/svg-animation-editor.html index 0bd5715f..944b2098 100644 --- a/svg-animation-editor.html +++ b/svg-animation-editor.html @@ -2,7 +2,7 @@ - + SVG Animation Editor @@ -236,6 +270,7 @@

SVG Animation Editor