← Back to Index

Lesson 10

SMIL Advanced

Lesson 9 covered <animate> and <set> - animating individual attributes with timing, easing, and repetition. This lesson completes the SMIL picture with the three remaining animation elements and the orchestration patterns that make complex, choreographed animations possible.

1. <animateTransform>

You can't animate the transform attribute with <animate> because transform values aren't simple numbers - they're function calls like rotate(45, 100, 100). The <animateTransform> element exists specifically for this. It takes a type attribute specifying which transform function to animate:

typefrom/to formatWhat it does
translate"tx ty"Moves the element
rotate"angle cx cy"Rotates angle degrees around point (cx, cy)
scale"sx sy"Scales (sy defaults to sx if omitted)
skewX"angle"Horizontal skew
skewY"angle"Vertical skew
Why no type="matrix"?

You might expect matrix to be a valid type, but it's not. The spec deliberately excludes it because the browser can't meaningfully interpolate between two arbitrary 6-value matrices - raw number interpolation produces weird non-linear warping (shearing mid-animation rather than smooth rotation or scaling). CSS solves this by decomposing matrices into individual functions before interpolating; SMIL doesn't do that decomposition. Instead, compose multiple <animateTransform> elements (one per function) with additive="sum". If you genuinely need arbitrary matrix interpolation, that's JavaScript territory (Web Animations API).

Rotation

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <rect x="75" y="75" width="50" height="50" fill="coral">
    <animateTransform
      attributeName="transform"
      type="rotate"
      from="0 100 100"
      to="360 100 100"
      dur="3s"
      repeatCount="indefinite"/>
  </rect>
</svg>
<!-- "0 100 100" means: 0 degrees rotation, centred on point (100,100)
     "360 100 100" means: 360 degrees rotation, same centre -->

Continuous rotation around the SVG centre (100,100). The from/to values include the rotation centre.

The rotation centre is part of the value

Unlike CSS transform-origin (which is set separately), SMIL's rotate type includes the centre coordinates in every value: "angle cx cy". This means you can even animate the rotation centre itself by changing cx/cy across keyframes - something CSS can't do mid-animation.

Scale (pulse effect)

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <!-- Group to set the scaling origin via translate -->
  <g transform="translate(100, 100)">
    <circle cx="0" cy="0" r="30" fill="#61afef">
      <animateTransform
        attributeName="transform"
        type="scale"
        values="1;1.4;1"
        dur="2s"
        repeatCount="indefinite"/>
    </circle>
  </g>
</svg>

Scale pulse. The element is centred at (0,0) inside a translated <g> so scaling happens from the centre rather than the top-left.

Scale origin gotcha

type="scale" scales from the coordinate system origin (0,0). If your element is at (100,100), it will fly away from the corner as it scales. The workaround: wrap in a <g transform="translate(cx, cy)"> and position the element at (0,0) within that group. Then scale happens from the element's visual centre.

Combining multiple transforms

By default, <animateTransform> replaces the entire transform attribute each frame. If you have two <animateTransform> elements on the same element, the second overwrites the first (not composing them). To stack transforms, set additive="sum" on the second one:

<rect x="-25" y="-25" width="50" height="50" fill="coral">
  <!-- First: rotate around origin -->
  <animateTransform attributeName="transform" type="rotate"
    from="0" to="360" dur="4s" repeatCount="indefinite"/>

  <!-- Second: scale pulse (additive="sum" to compose) -->
  <animateTransform attributeName="transform" type="scale"
    values="1;1.3;1" dur="1.5s" repeatCount="indefinite"
    additive="sum"/>
</rect>

Rotation + scale pulse composed via additive="sum". Without it, only the scale would run (it would replace the rotation).

additive="sum" is essential for composing transforms

This is where additive (from Lesson 9) becomes critical. Without it, multiple <animateTransform> elements fight over the same transform attribute and the last one wins. With additive="sum", they concatenate - exactly like writing transform="rotate(...) scale(...)" as a static attribute.

2. <animateMotion>

<animateMotion> moves an element along a path. Unlike animating cx/cy (which only gives you straight lines between keyframes), <animateMotion> follows arbitrary curves - Béziers, arcs, anything you can express as path data.

AttributePurposeExample
pathThe motion path (same syntax as <path d="...">)"M0,0 C50,-50 100,50 150,0"
rotateHow the element orients along the path"auto", "auto-reverse", or a fixed angle
keyPointsMap keyTimes to positions along the path (0–1)"0;0.3;1"
calcModeSame as <animate> (linear, paced, spline, discrete)"paced"

Basic motion along a curve

<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
  <!-- Show the path for reference -->
  <path d="M 50,150 C 100,20 300,20 350,150"
        fill="none" stroke="grey" stroke-dasharray="4 2"/>

  <circle r="10" fill="coral">
    <animateMotion
      path="M 50,150 C 100,20 300,20 350,150"
      dur="3s"
      repeatCount="indefinite"/>
  </circle>
</svg>

A circle following a cubic Bézier arc. The path is shown dashed for reference.

rotate="auto" - orienting along the path

By default, the element doesn't rotate as it moves - it just translates. Set rotate="auto" to make it orient tangent to the path (like a car following a road):

<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
  <path d="M 50,100 C 100,20 200,180 250,100 S 350,20 380,100"
        fill="none" stroke="grey" stroke-dasharray="4 2"/>

  <!-- Arrow shape that rotates to follow the path -->
  <polygon points="-8,-5 8,0 -8,5" fill="coral">
    <animateMotion
      path="M 50,100 C 100,20 200,180 250,100 S 350,20 380,100"
      dur="4s"
      rotate="auto"
      repeatCount="indefinite"/>
  </polygon>
</svg>

An arrow following an S-curve with rotate="auto". The arrow always points in the direction of travel.

rotate valueBehaviour
autoElement rotates to face the direction of motion (tangent to path)
auto-reverseSame as auto but flipped 180° (pointing backwards)
0 (or any number)Fixed rotation angle - element doesn't turn as it moves

Using <mpath> to reference an existing path

Instead of duplicating path data in the path attribute, you can reference a <path> element defined elsewhere using <mpath>:

<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="track" d="M 30,100 Q 200,20 370,100"/>
  </defs>

  <!-- Show the track -->
  <use href="#track" fill="none" stroke="grey" stroke-dasharray="4 2"/>

  <circle r="8" fill="#61afef">
    <animateMotion dur="3s" repeatCount="indefinite">
      <mpath href="#track"/>
    </animateMotion>
  </circle>
</svg>

<mpath href="#track"/> reuses an existing path definition. Single source of truth - change the path once, both the visual and the motion update.

calcMode="paced" is the default for animateMotion

Unlike <animate> (which defaults to linear), <animateMotion> defaults to paced. This means the element moves at constant velocity along the path regardless of how the control points are spaced. You don't need to set it explicitly - but be aware that adding keyTimes requires switching to calcMode="linear" or "spline" since paced ignores keyTimes.

3. Sequencing Animations (Reliable Patterns)

In Lesson 9, we discovered that the spec's syncbase timing (begin="animId.end") and cross-element event references (begin="otherId.click") don't work reliably in Chrome. This section covers the patterns that do work across browsers.

Pattern 1: Offset from document load

The simplest approach - stagger animations with absolute time offsets:

<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="15" fill="coral">
    <animate attributeName="cx" from="50" to="350"
             dur="1s" begin="0s" fill="freeze"/>
  </circle>
  <circle cx="50" cy="50" r="15" fill="#61afef">
    <animate attributeName="cx" from="50" to="350"
             dur="1s" begin="1s" fill="freeze"/>
  </circle>
  <circle cx="50" cy="50" r="15" fill="#98c379">
    <animate attributeName="cx" from="50" to="350"
             dur="1s" begin="2s" fill="freeze"/>
  </circle>
</svg>

Three circles staggered by 1s. Simple, reliable, works everywhere. Downside: if you change one duration, you must recalculate all offsets.

Pattern 2: Offset from a shared event

When the sequence should be user-triggered, offset all animations from the same click event:

<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg">
  <g style="cursor:pointer">
    <circle cx="50" cy="50" r="20" fill="coral">
      <animate attributeName="cx" from="50" to="350"
               dur="1s" begin="click" fill="freeze"/>
      <animate attributeName="r" from="20" to="10"
               dur="0.5s" begin="click+1s" fill="freeze"/>
      <animate attributeName="opacity" from="1" to="0.3"
               dur="0.5s" begin="click+1.5s" fill="freeze"/>
    </circle>
  </g>
</svg>
Click the circle: move (1s) → shrink (0.5s) → fade (0.5s)

Three-step sequence from one click. Each begin is offset from the same click event by the cumulative duration of prior steps.

Pattern 3: Looping sequences with begin lists

The begin attribute accepts a semicolon-separated list of start times. This lets you restart an animation at multiple points - useful for creating loops that include pauses:

<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="15" fill="coral">
    <!-- Runs at 0s, then again at 4s, then again at 8s... -->
    <animate attributeName="cx" from="50" to="350"
             dur="2s" fill="freeze"
             begin="0s; 4s; 8s; 12s"/>
    <!-- Snap back at 3s, 7s, 11s, 15s (between runs) -->
    <set attributeName="cx" to="50"
         begin="3s; 7s; 11s; 15s"/>
  </circle>
</svg>
Slides right (2s), pauses (1s), snaps back, repeats - using begin lists

The circle slides right every 4 seconds, with a 1-second pause before snapping back. Each start time is explicitly listed in begin.

begin lists are the workhorse for complex timing

Since syncbase timing is unreliable in browsers, begin lists are the most pragmatic way to schedule complex choreography. You explicitly list every start time. It's verbose but completely reliable. For simpler cases, a single begin offset with repeatCount is sufficient.

Ad

4. Event-Based Triggers

Beyond begin="click", SMIL supports several event types. The reliable pattern (from our Lesson 9 testing) is: the animation must be inside the element that receives the event.

EventFires whenWorks in Chrome?
clickElement is clickedYes (on parent element)
mouseoverCursor enters the elementYes (on parent element)
mouseoutCursor leaves the elementYes (on parent element)
mousedownMouse button pressed on elementYes (on parent element)
mouseupMouse button released on elementYes (on parent element)
focusinElement receives keyboard focus (requires tabindex attribute on the element)Yes

Hover effect with SMIL

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="100" r="40" fill="coral" style="cursor:pointer">
    <!-- Grow on hover -->
    <animate attributeName="r" from="40" to="55"
             dur="0.3s" begin="mouseover" fill="freeze"/>
    <!-- Shrink back on mouse leave -->
    <animate attributeName="r" from="55" to="40"
             dur="0.3s" begin="mouseout" fill="freeze"/>
  </circle>
</svg>

Hover over the circle - it grows. Move away - it shrinks. Pure SMIL hover effect, no CSS transitions needed.

SMIL hover vs CSS hover

For simple hover effects on CSS-animatable properties (opacity, transform), CSS :hover transitions are simpler and GPU-accelerated. SMIL hover is useful when you need to animate attributes CSS can't reach (like r, d, filter parameters) or want hover effects on standalone SVG files loaded via <img> (where CSS hover doesn't work because there's no external stylesheet). Note: <img> doesn't propagate mouse events, so SMIL hover won't work there either - use <object> or inline SVG.

5. Animating the viewBox (Zoom & Pan)

One of SMIL's unique capabilities - animating the viewBox attribute creates zoom and pan effects without changing any element's position:

<svg viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg"
     style="cursor:pointer">
  <!-- Scene content -->
  <circle cx="100" cy="100" r="20" fill="coral"/>
  <circle cx="300" cy="100" r="20" fill="#61afef"/>
  <rect x="180" y="80" width="40" height="40" fill="#98c379"/>

  <!-- Zoom into the green rect on click -->
  <animate attributeName="viewBox"
           from="0 0 400 200"
           to="160 60 80 80"
           dur="1.5s" begin="click" fill="freeze"/>
</svg>
Click to zoom into the green square

Click the SVG - the viewBox narrows to frame the green square, creating a smooth zoom effect. No element moves; only the camera does.

viewBox animation = virtual camera

Animating viewBox is like moving a camera over your scene. The four values are min-x min-y width height. Decreasing width/height zooms in; shifting min-x/min-y pans. This is uniquely powerful for storytelling animations - guiding the viewer's attention through a complex scene without moving any elements.

6. Practical Example: Orbiting Satellites

Combining <animateMotion>, <animateTransform>, and polyrhythmic timing for an organic orbital animation:

<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="orbit1" d="M 150,50 A 100,100 0 1 1 149.99,50" fill="none"/>
    <path id="orbit2" d="M 150,80 A 70,70 0 1 1 149.99,80" fill="none"/>
  </defs>

  <!-- Show orbits -->
  <use href="#orbit1" stroke="grey" stroke-dasharray="2 4" fill="none"/>
  <use href="#orbit2" stroke="grey" stroke-dasharray="2 4" fill="none"/>

  <!-- Central body -->
  <circle cx="150" cy="150" r="20" fill="#e5c07b"/>

  <!-- Satellite 1: outer orbit, 6s period -->
  <circle r="8" fill="coral">
    <animateMotion dur="6s" repeatCount="indefinite">
      <mpath href="#orbit1"/>
    </animateMotion>
  </circle>

  <!-- Satellite 2: inner orbit, 4s period, two-tone -->
  <g>
    <circle r="6" fill="#61afef"/>
    <path d="M 0,-6 A 6,6 0 0 1 0,6 Z" fill="#1a4a7a"/>
    <animateMotion dur="4s" repeatCount="indefinite">
      <mpath href="#orbit2"/>
    </animateMotion>
  </g>
</svg>

Two satellites: coral circle on the outer orbit (6s), blue square on the inner orbit (4s) spinning via additive="sum" (1.5s rotation). The square's corners make the spin clearly visible.

7. Practical Example: Stroke Drawing with Reveal

The classic "line drawing" animation - a path appears to draw itself, then a fill fades in:

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <path d="M 40,160 Q 100,20 160,160" fill="none"
        stroke="coral" stroke-width="3"
        stroke-dasharray="300" stroke-dashoffset="300">
    <!-- Draw the stroke -->
    <animate attributeName="stroke-dashoffset"
             from="300" to="0" dur="2s" fill="freeze"/>
    <!-- Then fill fades in -->
    <set attributeName="fill" to="rgba(255,127,80,0.2)"
         begin="2s" fill="freeze"/>
  </path>
</svg>

The path draws itself over 2 seconds (stroke-dashoffset animates from full to 0), then a translucent fill appears. This is the technique behind SVG logo animations.

The stroke drawing technique

Set stroke-dasharray equal to (or larger than) the path's total length, and stroke-dashoffset to the same value - this hides the stroke entirely. Animate stroke-dashoffset to 0 to "draw" it. You can find a path's length with pathElement.getTotalLength() in JS, or estimate it visually. This technique is covered in more depth in Lesson 11 (CSS animation of SVG).

8. Summary: The Four SMIL Animation Elements

ElementPurposeKey attributes
<animate>Animate any single attribute over timeattributeName, from/to/values, calcMode
<set>Discrete value change at a point in timeattributeName, to, begin
<animateTransform>Animate the transform attributetype (rotate/scale/translate/skew), additive
<animateMotion>Move element along a pathpath or <mpath>, rotate

All four share the same timing attributes: dur, begin, repeatCount, repeatDur, fill, calcMode, keyTimes, keySplines, additive, accumulate.

Quiz

Question 1

In <animateTransform type="rotate" from="0 100 100" to="360 100 100">, what do the 100 100 numbers represent?

The size of the rotation in pixels
The x,y coordinates of the rotation centre
The scale factor to apply after rotation
The translation to apply before rotation

Question 2

What happens if you have two <animateTransform> elements on one element without additive="sum"?

They compose automatically (both run)
The second one replaces the first (only one runs)
The browser throws an error and neither runs
They average their values for a blended result

Question 3

What does rotate="auto" on <animateMotion> do?

Rotates the element 360 degrees over the animation duration
Automatically calculates the shortest rotation path
Orients the element tangent to the path (faces direction of travel)
Randomly rotates the element at each keyframe

Question 4

What is the default calcMode for <animateMotion>?

linear (constant time per segment)
paced (constant velocity along entire path)
spline (eased with keySplines)
discrete (jumps between positions)

Question 5

What does animating the viewBox attribute achieve?

Resizes the SVG element on the HTML page
Moves all child elements to new positions
Creates a zoom/pan camera effect without moving any elements
Changes the coordinate system scale permanently

Exercises

  1. Spinning loader - Create a circle with a dashed stroke that rotates continuously using <animateTransform type="rotate">. Combine with stroke-dashoffset animation for the "material design" spinner effect.
  2. Following a figure-8 - Draw a figure-8 path and use <animateMotion> with rotate="auto" to move an arrow along it. Use <mpath> to reference the path.
  3. Composed transforms - Create a rectangle that simultaneously rotates (4s) and pulses its scale (1.5s) using two <animateTransform> elements with additive="sum".
  4. Click-triggered sequence - Build a 3-step animation triggered by click: (1) element moves right, (2) changes colour, (3) fades out. Use begin="click", begin="click+Xs" pattern.
  5. Zoom storytelling - Create a scene with 3 objects at different positions. Animate viewBox through a sequence of keyframes that zooms into each object in turn, pausing on each one. Use values with keyTimes.