SPB Git

spb/svgarden Public

SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)

HTML 79.2% Astro 10.3% JavaScript 6% CSS 3.7% Shell 0.8%

feat(snippets): add motion-path category (5)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 6 h ago (Aug 10, 2026) parent 22de6d6

Showing 5 changed files with +463 and −0

added snippets/motion-path/bee-flight.html +96 −0
@@ -0,0 +1,96 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/motion-path/bee-flight.html
7 + Desc : Erratic bee flight — darts and hovers paced with keyPoints/keyTimes
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Bee flight
12 +slug: bee-flight
13 +category: motion-path
14 +tags: [motion, smil, bee, pacing]
15 +difficulty: advanced
16 +techniques: [animateMotion, keyPoints, keyTimes, rotate-auto]
17 +how_it_works: >
18 + A plain <animateMotion> moves at constant speed, which reads as mechanical —
19 + real insects dart and hover. keyPoints/keyTimes decouple distance from time:
20 + keyPoints says how far along the path (0–1) the bee is at each keyTimes
21 + moment, so a big keyPoints jump inside a small keyTimes slice is a dart, and
22 + a near-zero jump over a long slice is a hover at a flower. calcMode="linear"
23 + keeps each segment steady so the contrast does the acting. The wings are a
24 + separate 90 ms CSS flutter — two timescales layered on one element sell the
25 + illusion.
26 +customizable:
27 + - { var: "--sg-color", label: "Bee", type: color, default: "#EFBB33" }
28 + - { var: "--sg-flower", label: "Flowers", type: color, default: "#7F77DD" }
29 + - { var: "--sg-size", label: "Size", type: range, min: 160, max: 440, default: 260, unit: px }
30 +created: 2026-08-10
31 +-->
32 +<div class="sg-bee-flight" style="--sg-color:#EFBB33; --sg-flower:#7F77DD; --sg-size:260px;">
33 + <svg viewBox="0 0 200 130" aria-hidden="true" focusable="false">
34 + <path id="sg-bee-flight-route" class="sg-bee-flight-route"
35 + d="M 20 100 C 40 60, 70 40, 96 52 C 118 62, 118 84, 100 92 C 130 98, 160 84, 172 56 C 178 42, 160 30, 140 38 C 100 26, 46 34, 20 100" />
36 + <g class="sg-bee-flight-flower"><circle cx="97" cy="55" r="4" /><circle cx="171" cy="55" r="4" /></g>
37 + <g class="sg-bee-flight-bee">
38 + <g class="sg-bee-flight-wings">
39 + <ellipse cx="-1.5" cy="-4.5" rx="3" ry="4" />
40 + <ellipse cx="2.5" cy="-4.5" rx="3" ry="4" />
41 + </g>
42 + <ellipse class="sg-bee-flight-body" cx="0" cy="0" rx="6" ry="4" />
43 + <path class="sg-bee-flight-stripes" d="M-2 -3.8 V3.8 M2 -3.8 V3.8" />
44 + <circle class="sg-bee-flight-eye" cx="5" cy="-1.2" r="0.9" />
45 + <animateMotion
46 + dur="9s" repeatCount="indefinite" rotate="auto" calcMode="linear"
47 + keyPoints="0; 0.3; 0.34; 0.62; 0.66; 0.9; 1"
48 + keyTimes="0; 0.1; 0.42; 0.52; 0.82; 0.92; 1">
49 + <mpath href="#sg-bee-flight-route" />
50 + </animateMotion>
51 + </g>
52 + </svg>
53 +</div>
54 +<style>
55 + .sg-bee-flight {
56 + width: var(--sg-size);
57 + }
58 + .sg-bee-flight svg {
59 + display: block;
60 + width: 100%;
61 + }
62 + .sg-bee-flight-route {
63 + fill: none;
64 + stroke: rgba(128, 128, 128, 0.3);
65 + stroke-width: 1;
66 + stroke-dasharray: 1.5 4;
67 + }
68 + .sg-bee-flight-flower circle {
69 + fill: var(--sg-flower);
70 + opacity: 0.8;
71 + }
72 + .sg-bee-flight-body {
73 + fill: var(--sg-color);
74 + }
75 + .sg-bee-flight-stripes {
76 + stroke: rgba(40, 40, 40, 0.75);
77 + stroke-width: 1.4;
78 + fill: none;
79 + }
80 + .sg-bee-flight-eye {
81 + fill: rgba(40, 40, 40, 0.75);
82 + }
83 + .sg-bee-flight-wings ellipse {
84 + fill: rgba(128, 128, 128, 0.4);
85 + transform-box: fill-box;
86 + transform-origin: center bottom;
87 + animation: sg-bee-flight-flutter 0.09s ease-in-out infinite alternate;
88 + }
89 + .sg-bee-flight-wings ellipse:nth-of-type(2) {
90 + animation-delay: 0.045s;
91 + }
92 + @keyframes sg-bee-flight-flutter {
93 + from { transform: scaleY(1); }
94 + to { transform: scaleY(0.45) rotate(8deg); }
95 + }
96 +</style>
added snippets/motion-path/comet-trail.html +90 −0
@@ -0,0 +1,90 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/motion-path/comet-trail.html
7 + Desc : Comet sweeping an arc with a fading clone-particle tail
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Comet trail
12 +slug: comet-trail
13 +category: motion-path
14 +tags: [motion, smil, comet, particles]
15 +difficulty: intermediate
16 +techniques: [animateMotion, begin-offset-clones, opacity-falloff, mpath]
17 +how_it_works: >
18 + The tail is not drawn — it is five clones of the head flying the exact same
19 + <animateMotion> route, each with a slightly less negative begin value, so
20 + every clone runs a fixed fraction of a second behind the one before it.
21 + Negative begins matter: they mean every particle is already mid-flight on
22 + the first frame, so nothing pops in from the SVG origin. Shrinking radii and
23 + falling opacity down the chain turn discrete circles into a convincing
24 + streak, and two CSS-twinkling stars give the sky depth for almost free.
25 +customizable:
26 + - { var: "--sg-color", label: "Comet", type: color, default: "#7F77DD" }
27 + - { var: "--sg-size", label: "Size", type: range, min: 160, max: 460, default: 280, unit: px }
28 +created: 2026-08-10
29 +-->
30 +<div class="sg-comet-trail" style="--sg-color:#7F77DD; --sg-size:280px;">
31 + <svg viewBox="0 0 200 110" aria-hidden="true" focusable="false">
32 + <path id="sg-comet-trail-arc" class="sg-comet-trail-arc" d="M -12 96 C 40 18, 130 6, 214 58" />
33 + <path class="sg-comet-trail-star" d="M40 78 l1.4 3 3 1.4 -3 1.4 -1.4 3 -1.4 -3 -3 -1.4 3 -1.4 Z" />
34 + <path class="sg-comet-trail-star sg-comet-trail-star2" d="M156 84 l1.2 2.6 2.6 1.2 -2.6 1.2 -1.2 2.6 -1.2 -2.6 -2.6 -1.2 2.6 -1.2 Z" />
35 + <circle class="sg-comet-trail-p5" r="0.8">
36 + <animateMotion dur="4s" begin="0s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
37 + </circle>
38 + <circle class="sg-comet-trail-p4" r="1.3">
39 + <animateMotion dur="4s" begin="-0.1s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
40 + </circle>
41 + <circle class="sg-comet-trail-p3" r="1.9">
42 + <animateMotion dur="4s" begin="-0.2s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
43 + </circle>
44 + <circle class="sg-comet-trail-p2" r="2.6">
45 + <animateMotion dur="4s" begin="-0.3s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
46 + </circle>
47 + <circle class="sg-comet-trail-halo" r="7">
48 + <animateMotion dur="4s" begin="-0.4s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
49 + </circle>
50 + <circle class="sg-comet-trail-head" r="4">
51 + <animateMotion dur="4s" begin="-0.4s" repeatCount="indefinite"><mpath href="#sg-comet-trail-arc" /></animateMotion>
52 + </circle>
53 + </svg>
54 +</div>
55 +<style>
56 + .sg-comet-trail {
57 + width: var(--sg-size);
58 + }
59 + .sg-comet-trail svg {
60 + display: block;
61 + width: 100%;
62 + }
63 + .sg-comet-trail-arc {
64 + fill: none;
65 + stroke: rgba(128, 128, 128, 0.18);
66 + stroke-width: 1;
67 + stroke-dasharray: 3 6;
68 + }
69 + .sg-comet-trail circle {
70 + fill: var(--sg-color);
71 + }
72 + .sg-comet-trail-head { opacity: 1; }
73 + .sg-comet-trail-halo { opacity: 0.18; }
74 + .sg-comet-trail-p2 { opacity: 0.6; }
75 + .sg-comet-trail-p3 { opacity: 0.4; }
76 + .sg-comet-trail-p4 { opacity: 0.25; }
77 + .sg-comet-trail-p5 { opacity: 0.12; }
78 + .sg-comet-trail-star {
79 + fill: var(--sg-color);
80 + opacity: 0.5;
81 + animation: sg-comet-trail-twinkle 2.6s ease-in-out infinite;
82 + }
83 + .sg-comet-trail-star2 {
84 + animation-delay: -1.3s;
85 + }
86 + @keyframes sg-comet-trail-twinkle {
87 + 0%, 100% { opacity: 0.5; transform: none; }
88 + 50% { opacity: 0.1; }
89 + }
90 +</style>
added snippets/motion-path/orbit-planets.html +111 −0
@@ -0,0 +1,111 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/motion-path/orbit-planets.html
7 + Desc : Mini solar system — planets on elliptical CSS offset-path orbits
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Orbiting planets
12 +slug: orbit-planets
13 +category: motion-path
14 +tags: [motion, orbit, planets, offset-path]
15 +difficulty: advanced
16 +techniques: [offset-path, offset-distance, "@supports", nested-transforms]
17 +how_it_works: >
18 + Each planet is pinned to an invisible ellipse with offset-path: path('…') and
19 + flown around it by animating a single property, offset-distance, from 0% to
20 + 100% — the browser handles all the trigonometry. The three orbits share one
21 + keyframe block and differ only in animation-duration, so their alignments
22 + drift like a real system. The moon is a nested transform: a carrier group
23 + rotating about its local origin, which offset-path has already parked on the
24 + planet. Everything sits inside an @supports guard, so browsers without
25 + offset-path simply show the planets resting on their rails.
26 +support: >
27 + CSS offset-path on SVG children needs Chrome 116+, Safari 16+ or Firefox 122+.
28 + Unsupported browsers show a static system (planets resting on their orbit
29 + rails); the moon keeps spinning either way.
30 +customizable:
31 + - { var: "--sg-sun", label: "Sun", type: color, default: "#EFBB33" }
32 + - { var: "--sg-color", label: "Planets", type: color, default: "#7F77DD" }
33 + - { var: "--sg-size", label: "Size", type: range, min: 140, max: 420, default: 240, unit: px }
34 + - { var: "--sg-duration", label: "Year", type: range, min: 2, max: 14, step: 0.5, default: 6, unit: s }
35 +created: 2026-08-10
36 +-->
37 +<div class="sg-orbit-planets" style="--sg-sun:#EFBB33; --sg-color:#7F77DD; --sg-size:240px; --sg-duration:6s;">
38 + <svg viewBox="0 0 200 140" aria-hidden="true" focusable="false">
39 + <ellipse class="sg-orbit-planets-rail" cx="100" cy="70" rx="38" ry="20" />
40 + <ellipse class="sg-orbit-planets-rail" cx="100" cy="70" rx="64" ry="36" />
41 + <ellipse class="sg-orbit-planets-rail" cx="100" cy="70" rx="88" ry="52" />
42 + <circle class="sg-orbit-planets-sun" cx="100" cy="70" r="9" />
43 + <circle class="sg-orbit-planets-p sg-orbit-planets-p1" r="3.4" />
44 + <g class="sg-orbit-planets-p sg-orbit-planets-p2">
45 + <circle class="sg-orbit-planets-body" r="4.6" />
46 + <g class="sg-orbit-planets-moon-orbit">
47 + <circle class="sg-orbit-planets-moon" cx="8.5" cy="0" r="1.6" />
48 + </g>
49 + </g>
50 + <circle class="sg-orbit-planets-p sg-orbit-planets-p3" r="2.6" />
51 + </svg>
52 +</div>
53 +<style>
54 + .sg-orbit-planets {
55 + width: var(--sg-size);
56 + }
57 + .sg-orbit-planets svg {
58 + display: block;
59 + width: 100%;
60 + }
61 + .sg-orbit-planets-rail {
62 + fill: none;
63 + stroke: rgba(128, 128, 128, 0.35);
64 + stroke-width: 1;
65 + stroke-dasharray: 2 3;
66 + }
67 + .sg-orbit-planets-sun {
68 + fill: var(--sg-sun);
69 + }
70 + .sg-orbit-planets-p,
71 + .sg-orbit-planets-body {
72 + fill: var(--sg-color);
73 + }
74 + .sg-orbit-planets-p1 { opacity: 0.9; transform: translate(62px, 70px); }
75 + .sg-orbit-planets-p2 { transform: translate(164px, 70px); }
76 + .sg-orbit-planets-p3 { opacity: 0.55; transform: translate(100px, 122px); }
77 + .sg-orbit-planets-moon {
78 + fill: var(--sg-color);
79 + opacity: 0.7;
80 + }
81 + .sg-orbit-planets-moon-orbit {
82 + transform-origin: 0 0;
83 + animation: sg-orbit-planets-spin 2.2s linear infinite;
84 + }
85 + @supports (offset-path: path('M0 0 L1 1')) {
86 + .sg-orbit-planets-p {
87 + transform: none;
88 + offset-rotate: 0deg;
89 + animation: sg-orbit-planets-fly var(--sg-duration) linear infinite;
90 + }
91 + .sg-orbit-planets-p1 {
92 + offset-path: path('M62 70 a38 20 0 1 1 76 0 a38 20 0 1 1 -76 0');
93 + }
94 + .sg-orbit-planets-p2 {
95 + offset-path: path('M36 70 a64 36 0 1 1 128 0 a64 36 0 1 1 -128 0');
96 + animation-duration: calc(var(--sg-duration) * 1.9);
97 + animation-delay: calc(var(--sg-duration) * -0.7);
98 + }
99 + .sg-orbit-planets-p3 {
100 + offset-path: path('M12 70 a88 52 0 1 1 176 0 a88 52 0 1 1 -176 0');
101 + animation-duration: calc(var(--sg-duration) * 3.1);
102 + animation-delay: calc(var(--sg-duration) * -1.4);
103 + }
104 + }
105 + @keyframes sg-orbit-planets-fly {
106 + to { offset-distance: 100%; }
107 + }
108 + @keyframes sg-orbit-planets-spin {
109 + to { transform: rotate(360deg); }
110 + }
111 +</style>
added snippets/motion-path/paper-plane-loop.html +68 −0
@@ -0,0 +1,68 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/motion-path/paper-plane-loop.html
7 + Desc : Paper plane flying a loop-the-loop, banking into the turns
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Paper plane loop
12 +slug: paper-plane-loop
13 +category: motion-path
14 +tags: [motion, smil, plane, loop]
15 +difficulty: advanced
16 +techniques: [animateMotion, rotate-auto, mpath, stroke-dashoffset]
17 +how_it_works: >
18 + SMIL's <animateMotion> drags the plane along a bézier flight path, and the
19 + one attribute doing the heavy lifting is rotate="auto": it continuously
20 + re-orients the element to the path's tangent, so the plane banks into the
21 + loop and exits nose-first instead of sliding sideways like a fridge magnet.
22 + The <mpath> child references the same path that is drawn as the dashed rail,
23 + so flight and artwork can never drift apart. A slow dashoffset animation on
24 + the rail suggests the airflow direction. Because the plane is drawn pointing
25 + along +x, "auto" needs no extra angle correction.
26 +customizable:
27 + - { var: "--sg-color", label: "Plane", type: color, default: "#7F77DD" }
28 + - { var: "--sg-size", label: "Width", type: range, min: 160, max: 460, default: 280, unit: px }
29 +created: 2026-08-10
30 +-->
31 +<div class="sg-paper-plane-loop" style="--sg-color:#7F77DD; --sg-size:280px;">
32 + <svg viewBox="0 0 200 120" aria-hidden="true" focusable="false">
33 + <path id="sg-paper-plane-loop-path" class="sg-paper-plane-loop-rail"
34 + d="M 10 82 C 40 58, 68 46, 96 54 C 128 64, 132 96, 110 94 C 88 92, 90 62, 116 50 C 138 40, 166 44, 192 58" />
35 + <g class="sg-paper-plane-loop-plane">
36 + <path d="M2 0 L-9 5 L-5.5 0 L-9 -5 Z" />
37 + <animateMotion dur="5s" repeatCount="indefinite" rotate="auto">
38 + <mpath href="#sg-paper-plane-loop-path" />
39 + </animateMotion>
40 + </g>
41 + </svg>
42 +</div>
43 +<style>
44 + .sg-paper-plane-loop {
45 + width: var(--sg-size);
46 + }
47 + .sg-paper-plane-loop svg {
48 + display: block;
49 + width: 100%;
50 + }
51 + .sg-paper-plane-loop-rail {
52 + fill: none;
53 + stroke: rgba(128, 128, 128, 0.4);
54 + stroke-width: 1.4;
55 + stroke-linecap: round;
56 + stroke-dasharray: 5 7;
57 + animation: sg-paper-plane-loop-wake 1.6s linear infinite;
58 + }
59 + .sg-paper-plane-loop-plane path {
60 + fill: var(--sg-color);
61 + stroke: var(--sg-color);
62 + stroke-width: 1;
63 + stroke-linejoin: round;
64 + }
65 + @keyframes sg-paper-plane-loop-wake {
66 + to { stroke-dashoffset: -12; }
67 + }
68 +</style>
added snippets/motion-path/train-track.html +98 −0
@@ -0,0 +1,98 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/motion-path/train-track.html
7 + Desc : Toy train following its track, wheels spinning, car in tow
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Train on track
12 +slug: train-track
13 +category: motion-path
14 +tags: [motion, smil, train, mpath]
15 +difficulty: intermediate
16 +techniques: [animateMotion, mpath, begin-offset, transform-box]
17 +how_it_works: >
18 + One <path> is the single source of truth: it is drawn twice as artwork
19 + (dashed sleepers under a solid rail) and referenced twice via <mpath> as the
20 + motion route, so the train can never leave its own track. The engine and the
21 + car run identical animateMotion timings, but the car starts with a negative
22 + begin offset, keeping it a fixed phase behind — that's how you couple
23 + carriages without maths. rotate="auto" turns both into the curves, while the
24 + wheels spin on their own axes thanks to transform-box: fill-box, which makes
25 + each wheel rotate around its own center instead of the SVG origin.
26 +customizable:
27 + - { var: "--sg-color", label: "Train", type: color, default: "#7F77DD" }
28 + - { var: "--sg-car", label: "Car", type: color, default: "#EFBB33" }
29 + - { var: "--sg-size", label: "Size", type: range, min: 160, max: 440, default: 260, unit: px }
30 +created: 2026-08-10
31 +-->
32 +<div class="sg-train-track" style="--sg-color:#7F77DD; --sg-car:#EFBB33; --sg-size:260px;">
33 + <svg viewBox="0 0 200 130" aria-hidden="true" focusable="false">
34 + <path id="sg-train-track-rail" class="sg-train-track-sleepers"
35 + d="M 45 25 H 155 A 28 28 0 0 1 183 53 V 77 A 28 28 0 0 1 155 105 H 45 A 28 28 0 0 1 17 77 V 53 A 28 28 0 0 1 45 25 Z" />
36 + <use href="#sg-train-track-rail" class="sg-train-track-rails" />
37 + <g class="sg-train-track-car">
38 + <rect x="-8" y="-8" width="16" height="6.5" rx="1.5" />
39 + <g class="sg-train-track-wheel"><circle cx="-4" cy="0" r="2.4" /><path d="M-4 -2.4 V2.4 M-6.4 0 H-1.6" /></g>
40 + <g class="sg-train-track-wheel"><circle cx="4" cy="0" r="2.4" /><path d="M4 -2.4 V2.4 M1.6 0 H6.4" /></g>
41 + <animateMotion dur="10s" begin="-9.75s" repeatCount="indefinite" rotate="auto">
42 + <mpath href="#sg-train-track-rail" />
43 + </animateMotion>
44 + </g>
45 + <g class="sg-train-track-engine">
46 + <rect x="-9" y="-8" width="13" height="7" rx="1.5" />
47 + <rect x="3" y="-11" width="7" height="10" rx="1.5" />
48 + <rect x="-7" y="-11.5" width="3" height="4" rx="1" />
49 + <g class="sg-train-track-wheel"><circle cx="-5" cy="0" r="2.4" /><path d="M-5 -2.4 V2.4 M-7.4 0 H-2.6" /></g>
50 + <g class="sg-train-track-wheel"><circle cx="5" cy="0" r="2.4" /><path d="M5 -2.4 V2.4 M2.6 0 H7.4" /></g>
51 + <animateMotion dur="10s" repeatCount="indefinite" rotate="auto">
52 + <mpath href="#sg-train-track-rail" />
53 + </animateMotion>
54 + </g>
55 + </svg>
56 +</div>
57 +<style>
58 + .sg-train-track {
59 + width: var(--sg-size);
60 + }
61 + .sg-train-track svg {
62 + display: block;
63 + width: 100%;
64 + }
65 + .sg-train-track-sleepers {
66 + fill: none;
67 + stroke: rgba(128, 128, 128, 0.35);
68 + stroke-width: 7;
69 + stroke-dasharray: 1.6 5;
70 + }
71 + .sg-train-track-rails {
72 + fill: none;
73 + stroke: rgba(128, 128, 128, 0.55);
74 + stroke-width: 1.6;
75 + stroke-dasharray: none;
76 + }
77 + .sg-train-track-engine rect {
78 + fill: var(--sg-color);
79 + }
80 + .sg-train-track-car rect {
81 + fill: var(--sg-car);
82 + }
83 + .sg-train-track-wheel circle {
84 + fill: rgba(60, 60, 60, 0.85);
85 + }
86 + .sg-train-track-wheel path {
87 + stroke: rgba(200, 200, 200, 0.9);
88 + stroke-width: 0.7;
89 + }
90 + .sg-train-track-wheel {
91 + transform-box: fill-box;
92 + transform-origin: center;
93 + animation: sg-train-track-spin 0.9s linear infinite;
94 + }
95 + @keyframes sg-train-track-spin {
96 + to { transform: rotate(360deg); }
97 + }
98 +</style>
99