← Back to Index

Lesson 13

Putting It Together

This lesson is different. No new concepts - instead, we build a complete, polished animated SVG from scratch, combining techniques from every lesson in the course. The project: an animated solar system with orbiting planets, a glowing sun, a streak comet, and interactive controls.

By the end you'll have a self-contained SVG that demonstrates: coordinate systems & viewBox (L1), transforms & groups (L2), shapes & paths (L3), styling (L4), gradients (L5), clipping (L6), filters (L7), text on a path (L8), SMIL motion (L9/L10), CSS animation (L11), and JavaScript interaction (L12).

1. The Finished Product

Here's what we're building. Study it, then we'll construct it piece by piece:

outer orbit • Saturn

The complete animated solar system. Sun pulses (SMIL), planets orbit (animateMotion), Saturn has a ring, comet follows its own path, text sits on the outer orbit, stars twinkle in the background. Click any planet for a highlight pulse.

2. Setting Up the Canvas

Every SVG project starts with the coordinate system (Lesson 1). We're using a 600×600 square viewBox - large enough for detail, simple coordinates, and it scales responsively via CSS.

<svg width="600" height="600" viewBox="0 0 600 600">
  <!-- Dark space background -->
  <rect width="600" height="600" fill="#0d1117"/>
</svg>
Lesson 1 in action

The viewBox defines our coordinate space. Setting width/height equal to the viewBox dimensions gives us a 1:1 pixel ratio at natural size, while allowing the SVG to scale responsively when constrained by CSS (max-width: 100%).

3. The Stars

Scattered small circles with low opacity create depth. We could generate these with JavaScript (Lesson 12), but for a self-contained file, hand-placed dots work well:

<g class="stars" opacity="0.6">
  <circle cx="45" cy="80" r="1" fill="white"/>
  <circle cx="120" cy="30" r="0.8" fill="white"/>
  <circle cx="200" cy="55" r="1.2" fill="white"/>
  <!-- ... more scattered across the 600x600 space -->
</g>

Group opacity (Lesson 2) lets us dim all stars at once. Varying the radius slightly (0.7–1.2) creates natural variation without needing different fills or filters.

4. Orbit Paths

Each orbit is a circular arc path defined in <defs> (Lesson 3) and reused multiple times: once as a visible dashed track, and again as a motion path for the planet.

<defs>
  <!-- Circle as a path (for animateMotion) -->
  <!-- Arc from top, sweeps full circle back to start -->
  <path id="orbit-1" d="M 300,165 A 135,135 0 1 1 299.99,165" fill="none"/>
  <path id="orbit-2" d="M 300,110 A 190,190 0 1 1 299.99,110" fill="none"/>
  <path id="orbit-3" d="M 300,60 A 240,240 0 1 1 299.99,60" fill="none"/>
</defs>

<!-- Visible orbit tracks (dashed) -->
<use href="#orbit-1" stroke="#334155" stroke-width="0.5" stroke-dasharray="4 4" opacity="0.5"/>
<use href="#orbit-2" stroke="#334155" stroke-width="0.5" stroke-dasharray="4 4" opacity="0.5"/>
<use href="#orbit-3" stroke="#334155" stroke-width="0.5" stroke-dasharray="4 4" opacity="0.5"/>
Why paths instead of <circle> for orbits?

<animateMotion> needs a <path> element (via <mpath>). You can't use a <circle> directly as a motion path. The arc command A 135,135 0 1 1 299.99,165 traces a full circle - note the tiny offset (299.99 instead of 300) which is needed because an arc can't start and end at the same point (it would be zero-length).

5. The Sun

The sun combines a radial gradient (Lesson 5), a glow filter (Lesson 7), and a gentle SMIL pulse (Lesson 9):

<defs>
  <!-- Glow: blur + layer original on top -->
  <filter id="sun-glow" x="-50%" y="-50%" width="200%" height="200%">
    <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/>
    <feMerge>
      <feMergeNode in="blur"/>
      <feMergeNode in="SourceGraphic"/>
    </feMerge>
  </filter>

  <!-- Hot centre fading to golden edge -->
  <radialGradient id="sun-grad" cx="50%" cy="50%" r="50%">
    <stop offset="0%" stop-color="#fff7e6"/>
    <stop offset="40%" stop-color="#fbbf24"/>
    <stop offset="100%" stop-color="#f59e0b"/>
  </radialGradient>
</defs>

<!-- Sun: gradient fill + glow filter + breathing animation -->
<circle cx="300" cy="300" r="45" fill="url(#sun-grad)" filter="url(#sun-glow)">
  <animate attributeName="r" values="45;48;45" dur="3s" repeatCount="indefinite"/>
</circle>

The filter region is expanded (x="-50%" width="200%") so the blur doesn't get clipped at the default 10% boundary. The feMerge composites the blurred version behind the sharp original - creating a glow halo without losing the sun's crisp edge.

6. Orbiting Planets

Each planet uses <animateMotion> with <mpath> (Lesson 10) to follow its orbit path. Different durations create the feel of inner planets moving faster:

<!-- Mercury: fast inner orbit -->
<circle r="6" fill="#a8a29e">
  <animateMotion dur="4s" repeatCount="indefinite">
    <mpath href="#orbit-1"/>
  </animateMotion>
</circle>

<!-- Earth: medium orbit with gradient fill -->
<circle r="10" fill="url(#earth-grad)">
  <animateMotion dur="8s" repeatCount="indefinite">
    <mpath href="#orbit-2"/>
  </animateMotion>
</circle>

<!-- Saturn: slow outer orbit with ring -->
<g>
  <animateMotion dur="15s" repeatCount="indefinite">
    <mpath href="#orbit-3"/>
  </animateMotion>
  <circle r="14" fill="#e5c07b"/>
  <!-- Ring (next section) -->
</g>
Lesson 10 in action: animateMotion defaults to paced

We don't set calcMode - it defaults to paced, giving constant velocity around the orbit regardless of how the arc control points are spaced. This is what makes the motion feel natural.

Ad

7. Saturn's Ring

The ring is an ellipse with a clipPath (Lesson 6) that hides the top half - creating the illusion that the ring passes behind the planet:

<defs>
  <!-- Only show the bottom half of the ring -->
  <clipPath id="ring-clip">
    <rect x="-30" y="2" width="60" height="30"/>
  </clipPath>
</defs>

<!-- Inside Saturn's <g> (which is already moving along orbit-3) -->
<g clip-path="url(#ring-clip)">
  <ellipse cx="0" cy="0" rx="24" ry="8"
           fill="none" stroke="#c2a045" stroke-width="3" opacity="0.7"/>
</g>

The clip rect starts at y="2" (just below centre), so the top arc of the ellipse is hidden behind the planet body. Since everything is positioned at (0,0) within the group, it moves with Saturn automatically.

8. The Comet

The comet combines motion paths (Lesson 10), a blur filter (Lesson 7), and a stroke-based tail effect (Lesson 11):

<defs>
  <!-- Irregular comet trajectory -->
  <path id="comet-path"
        d="M 50,50 C 200,100 500,150 550,500 C 400,550 100,400 50,50"/>
  <!-- Soft blur for the comet head -->
  <filter id="comet-blur" x="-20%" y="-20%" width="140%" height="140%">
    <feGaussianBlur stdDeviation="2"/>
  </filter>
</defs>

<!-- Comet head (glowing dot) -->
<g filter="url(#comet-blur)" opacity="0.9">
  <circle r="3" fill="white">
    <animateMotion dur="6s" repeatCount="indefinite" rotate="auto">
      <mpath href="#comet-path"/>
    </animateMotion>
  </circle>
</g>

<!-- Comet tail (animated dash along the same path) -->
<use href="#comet-path" fill="none" stroke="white" stroke-width="1.5"
     opacity="0.4" stroke-linecap="round" stroke-dasharray="50 550">
  <animate attributeName="stroke-dashoffset"
           from="0" to="-600" dur="6s" repeatCount="indefinite"/>
</use>

The tail uses stroke-dasharray="50 550" - a 50-unit visible dash followed by a 550-unit gap. Animating stroke-dashoffset from 0 to -600 (the path's approximate length) makes that dash slide along the path at the same speed as the comet head, creating the illusion of a trailing streak.

9. Text on the Orbit

A label riding the outer orbit path, using <textPath> (Lesson 8):

<text font-size="8" fill="#64748b" font-family="monospace">
  <textPath href="#orbit-3" startOffset="15%">
    outer orbit &bull; Saturn
  </textPath>
</text>

The text follows the curve of orbit-3, positioned at 15% along the path. It's styled subtly (small, muted colour) so it adds information without competing with the animation.

10. Adding Interactivity

Finally, we add JavaScript controls (Lesson 12) - play/pause and speed adjustment. This bridges the static SMIL animation into something the user can control:

<script>
const svg = document.getElementById('solar-system');

function togglePause() {
  if (svg.animationsPaused()) {
    svg.unpauseAnimations();
  } else {
    svg.pauseAnimations();
  }
}

function setSpeed(rate) {
  // SMIL doesn't have a native speed control, but we can
  // manipulate the document time to simulate it.
  // For true speed control, use WAAPI instead of SMIL.
  // Here we simply pause/unpause as a basic control.
}

// Click any planet to highlight it
svg.addEventListener('click', (e) => {
  const planet = e.target.closest('circle[r]');
  if (!planet || planet.closest('g.stars')) return;

  // Pulse effect via WAAPI
  planet.animate([
    { filter: 'brightness(1)' },
    { filter: 'brightness(2)' },
    { filter: 'brightness(1)' }
  ], { duration: 500 });
});
</script>
The three animation systems working together

In this project: SMIL handles continuous motion (orbit paths, sun pulse) because it's declarative and self-contained. CSS could replace the sun pulse with @keyframes if you prefer. JavaScript adds user-driven interaction (pause/play, click highlights) that neither SMIL nor CSS can do alone. This is the pragmatic split for real projects.

11. Skills Checklist

Building (or studying) this project exercises every major SVG skill from the course:

SkillWhere it appearsLesson
Coordinate system & viewBox600×600 canvas, responsive scaling1
Transforms & groupingSaturn's group (ring + planet move together)2
Shapes & pathsCircles (planets), arcs (orbits), curves (comet path)3
Stylingstroke-dasharray on orbits, fill/opacity choices4
GradientsSun (radial), Earth (radial with offset focal)5
ClippingSaturn's ring (bottom-half clip)6
FiltersSun glow (blur + merge), comet blur7
Text & textPathLabel on outer orbit curve8
SMIL fundamentalsSun radius pulse (<animate>)9
SMIL advancedPlanet orbits (<animateMotion> + <mpath>)10
CSS animationComet tail (stroke-dasharray technique)11
JavaScript + SVGPause/play, click-to-highlight (WAAPI)12

Alternative Capstone Projects

The solar system is one way to combine everything. Here are three alternative projects you could tackle independently, each exercising the full skill set:

  1. Animated logo reveal - Design a logo with 5+ paths. Use the stroke-drawing technique to reveal each path sequentially (CSS), then fade in fills (CSS), then add a subtle continuous idle animation (SMIL rotation or floating). Add a <textPath> tagline along a curve. Include a radial gradient background, a drop-shadow filter on the logo, and a JS "replay" button.
  2. Interactive weather scene - Build a landscape with sun, clouds, rain, and lightning. Use clipping for the horizon line, gradients for the sky, filters for cloud softness, CSS keyframes for rain drops, SMIL for cloud drift, and JavaScript buttons to switch between sunny/rainy/stormy states (toggling element visibility and animation playback).
  3. Data dashboard card - Create a single KPI card with: a line chart (path), animated bar chart (rects with CSS staggered entrance), a circular progress indicator (stroke-dashoffset), gradient fills, a filter-based shadow, text labels, and JavaScript that generates the chart from an array of data values. Add a "refresh" button that regenerates random data and re-animates with WAAPI.