← Back to Index

Lesson 9

SMIL Animation Fundamentals

SMIL (Synchronized Multimedia Integration Language) gives SVG a declarative animation system that lives inside the markup itself. No JavaScript, no external CSS files - the animation is part of the SVG document. While CSS @keyframes inside a <style> block can also produce self-contained animations, SMIL can target any SVG attribute - including geometry (cx, d, viewBox), filter parameters (stdDeviation), and text attributes (startOffset) that CSS simply can't reach. For truly self-contained animated SVGs that go beyond what CSS properties allow, SMIL is the tool.

This lesson covers the core <animate> element and the timing model that drives it. Lesson 10 will build on this with <animateTransform>, <animateMotion>, and advanced timing (syncbase, events, orchestration).

1. The Mental Model

A SMIL animation is a child element nested inside the element it animates. It declares: "animate this attribute, from this value to that value, over this duration." The browser handles interpolation.

Animations are children of their targets

Unlike CSS @keyframes (which are defined globally and applied via class), SMIL animations live inside the element they affect. This is what makes them self-contained - move or copy the element, and its animation comes with it. The animation element overrides the attribute value for its duration, then (by default) the attribute snaps back to its original value.

<circle cx="50" cy="50" r="20" fill="coral">
  <!-- This animate element is a CHILD of the circle -->
  <animate
    attributeName="r"
    from="20"
    to="40"
    dur="2s"
    repeatCount="indefinite"/>
</circle>

A pulsing circle - the simplest possible SMIL animation. The radius animates from 20 to 40, looping indefinitely.

2. The <animate> Element

<animate> is the workhorse. It animates a single attribute of its parent element over time. Here are its core attributes:

AttributePurposeExample
attributeNameWhich attribute to animate"cx", "fill", "opacity"
fromStarting value"0"
toEnding value"100"
byRelative change (alternative to to)"50" (adds 50 to current)
valuesSemicolon-separated keyframe values (overrides from/to)"0;100;50"
durDuration (clock value)"3s", "500ms", "0:02:30"
beginWhen to start"0s", "2s", "click"
repeatCountHow many times to repeat"3", "indefinite"
repeatDurTotal time to keep repeating"10s"
fillWhat happens when animation ends"freeze" or "remove"
fill="freeze" vs fill="red"

The fill attribute on an animation element means something completely different from fill on a shape. On <animate>, it controls whether the final animated value is held (freeze) or removed (remove, the default - snaps back to the original). This is a common source of confusion.

from/to vs values

You can define an animation two ways:

<!-- Two-value animation (from/to) -->
<rect width="50" height="50" fill="coral">
  <animate attributeName="x" from="0" to="300" dur="3s"
           repeatCount="indefinite"/>
</rect>

<!-- Multi-value animation (values) -->
<rect width="50" height="50" fill="#61afef">
  <animate attributeName="x" values="0;300;150;300;0" dur="4s"
           repeatCount="indefinite"/>
</rect>
Top: from/to (linear) | Bottom: values (multi-keyframe bounce)

Top: simple linear motion. Bottom: multi-keyframe path creating a bouncing pattern.

3. Timing: dur, begin, repeatCount

Clock values (dur)

Duration uses clock-value syntax:

FormatExampleMeaning
Seconds3s3 seconds
Milliseconds500msHalf a second
Minutes0:30 or 30s30 seconds
Full clock0:01:301 minute 30 seconds

begin - when the animation starts

The begin attribute is surprisingly powerful. At its simplest, it's a time offset from document load:

<!-- Starts immediately -->
<animate ... begin="0s"/>

<!-- Starts 2 seconds after document load -->
<animate ... begin="2s"/>

<!-- Starts when parent element is clicked -->
<animate ... begin="click"/>

Click-to-start animation

Setting begin="click" means the animation waits until the user clicks the parent element (the one the <animate> is nested inside). No JavaScript needed:

<svg viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
  <circle cx="80" cy="60" r="30" fill="coral" style="cursor:pointer">
    <animate attributeName="cx" from="80" to="320" dur="1s"
             begin="click" fill="freeze"/>
  </circle>
  <text x="200" y="110" text-anchor="middle"
        font-size="12">Click the circle</text>
</svg>
Click the circle

Click the coral circle - it slides to the right. No JavaScript, no event listeners. begin="click" is pure SMIL.

Chaining animations with event offsets

You can offset from any event trigger. begin="click+1.5s" means "1.5 seconds after the click." This lets you sequence multiple animations from a single user action:

<svg viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="60" r="20" fill="coral" style="cursor:pointer">
    <!-- Step 1: move right (starts on click) -->
    <animate attributeName="cx" from="50" to="350" dur="1.5s"
             begin="click" fill="freeze"/>
    <!-- Step 2: shrink 1.5s after click (= when move finishes) -->
    <animate attributeName="r" from="20" to="8"
             dur="0.5s" begin="click+1.5s" fill="freeze"/>
  </circle>
</svg>
Click the circle: moves right (1.5s), THEN shrinks (0.5s)

Click the coral circle. It moves right, then shrinks - the second animation uses begin="click+1.5s" to start exactly when the first finishes.

Syncbase timing (preview)

The spec also defines syncbase timing - referencing another animation by id:

<!-- Syncbase syntax (Lesson 10 will cover this in depth) -->
<animate id="anim1" attributeName="cx" from="50" to="300"
         dur="2s" fill="freeze"/>

<!-- Starts when anim1 ends -->
<animate ... begin="anim1.end"/>

<!-- Starts 0.5s AFTER anim1 ends -->
<animate ... begin="anim1.end+0.5s"/>
Syncbase timing browser support

The spec defines begin="animId.end" for chaining, but browser support is inconsistent - even Chrome can fail to fire syncbase-referenced animations on nested sibling <animate> elements targeting the same parent. The reliable cross-browser alternative is the event offset pattern shown above (begin="click+1.5s"). We'll explore syncbase workarounds and more robust sequencing patterns in Lesson 10.

repeatCount - how many cycles

4. The fill Attribute (Animation)

When a non-repeating animation ends, what should happen to the attribute?

ValueBehaviourUse case
removeAttribute snaps back to its base value (default)Temporary effects, attention pulses
freezeAttribute holds its final animated valueOne-shot transitions, reveals, state changes
<svg viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
  <!-- fill="remove" (default)  -  snaps back -->
  <circle cx="80" cy="60" r="30" fill="coral">
    <animate attributeName="r" from="30" to="10" dur="2s"
             fill="remove" repeatCount="1"/>
  </circle>

  <!-- fill="freeze"  -  stays at final value -->
  <circle cx="250" cy="60" r="30" fill="#61afef">
    <animate attributeName="r" from="30" to="10" dur="2s"
             fill="freeze" repeatCount="1"/>
  </circle>
</svg>
fill="remove" fill="freeze"

Both shrink from r=30 to r=10 over 2s. Left snaps back to 30 after; right stays at 10 permanently.

5. Interpolation: calcMode

The calcMode attribute controls how the browser interpolates between values. This is SMIL's easing system.

ValueBehaviourCSS equivalent
linearConstant speed between each pair of values (default for most attributes)linear
discreteJumps between values with no interpolation (default for non-numeric attributes like fill colour changes)steps(1)
pacedConstant velocity across all segments (ignores keyTimes)No direct equivalent
splineCubic Bézier easing between segments (requires keySplines)cubic-bezier(...)
paced is unique to SMIL

calcMode="paced" ensures constant velocity regardless of the spacing of your keyframe values. If your values are "0;100;300", a linear animation would spend equal time on each segment (fast on the long segment, slow on the short one). paced adjusts timing so the element moves at the same speed throughout. This is invaluable for smooth motion animations.

calcMode="spline" with keySplines

For custom easing curves, set calcMode="spline" and provide keySplines. This uses the same cubic Bézier model as CSS cubic-bezier().

Each spline is four numbers: x1 y1 x2 y2 - the two control points of a cubic Bézier on a unit square where:

This is a timing function, not a spatial curve. Unlike the Bézier curves in path commands (which define physical shapes in x/y space), this Bézier maps time to progress. The slope of the curve at any point represents the instantaneous velocity - a steep section means fast movement, a flat section means slow. You're not defining velocity directly; you're shaping the time-to-progress relationship, and velocity is its derivative.

EasingkeySplines valueMeaning
ease-in0.42 0 1 1Starts slow, ends at full speed
ease-out0 0 0.58 1Starts at full speed, decelerates
ease-in-out0.42 0 0.58 1Slow start and end, fast middle
ease (CSS default)0.25 0.1 0.25 1Gentle version of ease-in-out
overshoot0.6 -0.28 0.74 0.05Goes past the target then settles

Semicolons separate segments. If values has N entries, there are N−1 segments, so you need N−1 splines. For values="A;B;C" (2 segments): keySplines="<A→B curve>; <B→C curve>".

<svg viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
  <!-- Linear (for comparison) -->
  <circle cx="30" cy="40" r="15" fill="grey" opacity="0.4">
    <animate attributeName="cx" values="30;370;30"
             dur="3s" repeatCount="indefinite"/>
  </circle>

  <!-- Spline-eased -->
  <circle cx="30" cy="80" r="15" fill="coral">
    <animate attributeName="cx"
             values="30;370;30"
             dur="3s"
             calcMode="spline"
             keyTimes="0;0.5;1"
             keySplines="0.42 0 1 1; 0 0 0.58 1"
             repeatCount="indefinite"/>
  </circle>
</svg>
grey = linear coral = ease-in / ease-out (spline)

Both travel the same distance in the same time. The spline-eased circle accelerates and decelerates naturally.

6. keyTimes - Controlling Timing

keyTimes maps each value in the values list to a point in normalised time (0 to 1). It lets you spend more time on some segments and less on others.

<!-- values has 4 entries, so keyTimes needs 4 entries -->
<animate attributeName="cx"
         values="50;200;350;200"
         keyTimes="0;0.2;0.5;1"
         dur="4s"
         repeatCount="indefinite"/>
<!-- 
  0 → 0.2 (20% of 4s = 0.8s): cx goes 50 → 200 (fast)
  0.2 → 0.5 (30% of 4s = 1.2s): cx goes 200 → 350 (medium)
  0.5 → 1.0 (50% of 4s = 2.0s): cx goes 350 → 200 (slow)
-->
Rules for keyTimes

The number of keyTimes values must equal the number of values. The first must be 0, the last must be 1 (for linear/spline modes). For discrete mode, the last doesn't have to be 1. For paced mode, keyTimes is ignored entirely.

Ad

7. The <set> Element

<set> is a simplified animation that performs a discrete value change at a point in time. No interpolation, no duration needed - it just switches an attribute to a new value. Think of it as calcMode="discrete" with a single target value.

<circle cx="200" cy="60" r="30" fill="coral">
  <!-- Change colour to blue after 2 seconds -->
  <set attributeName="fill" to="#61afef" begin="2s" fill="freeze"/>
</circle>
Turns blue after 2 seconds (one-shot, no interpolation)

<set> is ideal for visibility toggles, colour state changes, and triggering discrete property switches.

<set> fully supports the begin attribute with all the same values as <animate> - time offsets, click events, and syncbase timing all work. This makes it ideal for click-driven state changes:

<!-- Colour change on click -->
<circle cx="100" cy="60" r="30" fill="coral" style="cursor:pointer">
  <set attributeName="fill" to="#61afef" begin="click" fill="freeze"/>
</circle>

<!-- Visibility toggle after a delay -->
<rect x="10" y="10" width="80" height="40" fill="coral">
  <set attributeName="visibility" to="hidden" begin="3s" fill="freeze"/>
</rect>

Cross-element triggers

The spec defines begin="elementId.click" for triggering an animation from a click on a different element. However, browser support for cross-element event references is unreliable (Chrome often ignores them). The workaround is to place the <set> inside the clickable element itself and target a different attribute, or use begin="click" directly on each element that should react. We'll explore cross-element orchestration patterns in Lesson 10.

For now, the reliable pattern is: <set> nested inside the element being clicked, controlling that same element:

<svg viewBox="0 0 300 100" xmlns="http://www.w3.org/2000/svg">
  <!-- Click to change colour (set is inside the clickable element) -->
  <rect x="50" y="20" width="200" height="60" rx="8"
        fill="coral" style="cursor:pointer">
    <set attributeName="fill" to="#61afef" begin="click" fill="freeze"/>
  </rect>
  <text x="150" y="57" text-anchor="middle"
        font-size="16" fill="white" pointer-events="none">Click me</text>
</svg>
Click me

Click the rectangle - it turns blue permanently. <set> with begin="click" and fill="freeze" makes a one-shot state change.

Common uses for <set>:

8. Animating Colour

Colour attributes (fill, stroke, stop-color) can be smoothly interpolated with <animate>. The browser handles the colour space interpolation:

<circle cx="200" cy="60" r="40" fill="coral">
  <animate attributeName="fill"
           values="coral;#61afef;#98c379;#e5c07b;coral"
           dur="6s" repeatCount="indefinite"/>
</circle>

Smooth colour cycling through four colours. The browser interpolates between hex values in sRGB space.

9. Additive and Accumulative Animation

Two lesser-known but powerful attributes control how animated values combine:

AttributeValuesEffect
additivereplace (default) / sumsum: animated value is added to the base value instead of replacing it
accumulatenone (default) / sumsum: each repeat cycle starts from where the last one ended (values accumulate)

additive and accumulate answer different questions:

Think of it this way: additive controls the spatial reference (relative to where?) and accumulate controls the temporal reference (does each repeat reset or build?).

Concrete example - <rect x="100"> with from="0" to="50", repeatCount="3":

SettingsCycle 1Cycle 2Cycle 3
Neither (defaults)x: 0→50x: 0→50x: 0→50
additive="sum"x: 100→150x: 100→150x: 100→150
accumulate="sum"x: 0→50x: 50→100x: 100→150
Both sumx: 100→150x: 150→200x: 200→250

accumulate example

<!-- Without accumulate: bounces between 0 and 100 each cycle -->
<rect x="0" y="10" width="30" height="30" fill="coral">
  <animate attributeName="x" from="0" to="100" dur="2s"
           repeatCount="3" fill="freeze"/>
</rect>

<!-- With accumulate="sum": each cycle adds 100 more -->
<rect x="0" y="50" width="30" height="30" fill="#61afef">
  <animate attributeName="x" from="0" to="100" dur="2s"
           repeatCount="3" fill="freeze"
           accumulate="sum"/>
</rect>
No accumulate: resets each cycle accumulate="sum": each cycle adds 100

Top resets to 0 each cycle; bottom starts each cycle where the last ended (0→100, 100→200, 200→300).

additive example

<!-- additive="replace" (default): animated value IS the x value -->
<rect x="100" y="10" width="30" height="30" fill="coral">
  <animate attributeName="x" from="0" to="50" dur="2s"
           repeatCount="indefinite"/>
  <!-- x goes 0→50, ignoring the base x="100" -->
</rect>

<!-- additive="sum": animated value is ADDED to base x="100" -->
<rect x="100" y="60" width="30" height="30" fill="#61afef">
  <animate attributeName="x" from="0" to="50" dur="2s"
           additive="sum" repeatCount="indefinite"/>
  <!-- x goes (100+0)→(100+50) = 100→150 -->
</rect>
base x=100 replace: ignores base, goes 0→50 sum: adds to base, goes 100→150

Both animate from="0" to="50". With replace, the rect jumps to x=0 then goes to 50 (ignoring base). With sum, the rect starts at its base (100) and shifts +0 to +50 from there.

10. Multiple Animations on One Element

You can stack multiple <animate> children on the same element, each targeting a different attribute. They run independently and simultaneously:

<circle cx="200" cy="60" r="20" fill="coral">
  <!-- Animate position -->
  <animate attributeName="cx" values="50;350;50"
           dur="4s" repeatCount="indefinite"/>
  <!-- Animate size simultaneously -->
  <animate attributeName="r" values="20;35;20"
           dur="2s" repeatCount="indefinite"/>
  <!-- Animate opacity simultaneously -->
  <animate attributeName="opacity" values="1;0.4;1"
           dur="4s" repeatCount="indefinite"/>
</circle>

Three independent animations on one circle: position (4s cycle), radius (2s cycle), opacity (4s cycle). Different durations create complex-looking motion from simple rules.

Polyrhythm principle

Using different dur values on multiple animations of the same element creates polyrhythmic motion - the overall pattern doesn't repeat until the least common multiple of all durations. A 3s and 4s animation won't sync up until 12s. This is a cheap way to create organic, non-repetitive-looking animation from very simple declarations.

11. SMIL and the CSS Cascade

You might worry: if CSS overrides presentation attributes (Lesson 4), and SMIL animates attributes, won't CSS block SMIL? No - SMIL operates at a higher priority than both.

The actual priority order (highest wins):

  1. SMIL animation (special "override" layer - always wins while active)
  2. CSS !important
  3. CSS specificity (stylesheets, <style> blocks)
  4. Presentation attributes (fill="coral")
  5. Inherited values
<style>
  .styled { fill: blue; }  /* CSS wins over presentation attr */
</style>

<circle class="styled" fill="red" cx="100" cy="60" r="30">
  <!-- SMIL wins over CSS  -  circle animates to green -->
  <animate attributeName="fill" to="green" dur="2s"
           fill="freeze"/>
</circle>
<!-- Priority: SMIL (green) > CSS (blue) > attribute (red) -->
SMIL always wins while active

The SVG spec defines SMIL animations as applying after the CSS cascade resolves - they operate in a special override step. This means you can safely use CSS for static styling and SMIL for animation on the same element. When a fill="remove" animation ends, CSS takes back control. When fill="freeze", SMIL holds indefinitely.

This only matters for shared properties

The CSS/SMIL conflict only arises for properties that exist in both worlds (like fill, stroke, opacity). Geometry attributes like cx, cy, r, d, viewBox are pure SVG attributes with no CSS equivalent - CSS can't set them, so there's never a conflict. SMIL animates them freely.

12. What Can (and Can't) Be Animated

SMIL can animate any presentation attribute or geometry attribute that takes a numeric or colour value. Some examples:

AnimatableExamples
Geometryx, y, cx, cy, r, rx, ry, width, height, points, d (path data!)
Paintfill, stroke, stop-color, flood-color
Opacityopacity, fill-opacity, stroke-opacity
Strokestroke-width, stroke-dasharray, stroke-dashoffset
Transformsvia <animateTransform> (Lesson 10)
Filter paramsstdDeviation, dx/dy on filter prims, etc.
TextstartOffset, textLength, dx, dy
viewBoxYes! Animating viewBox creates zoom/pan effects
Not directly animatable with SMILWorkaround
class attributeUse <set> to swap class names
CSS custom propertiesAnimate the underlying attribute instead
transform (as a string)Use <animateTransform>
clip-path / mask referencesAnimate the shapes inside the clip/mask
Shape morphing with d and points

You can animate the d attribute of a <path> to morph between shapes. The constraint: both path data strings must have the same number and type of commands. You can't morph a path with 3 segments into one with 7 - the browser needs a 1:1 mapping between points. The points attribute on <polygon>/<polyline> is simpler - just match the number of coordinate pairs (no command types to worry about). Both are the basis of shape morphing animations (Lesson 13 capstone material).

13. Practical Example: Breathing Circle

Combining the techniques covered so far into a polished, production-quality animation:

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="100" r="40" fill="none"
          stroke="coral" stroke-width="3" opacity="0.8">
    <!-- Radius breathes in and out -->
    <animate attributeName="r" values="40;60;40"
             dur="4s" calcMode="spline"
             keyTimes="0;0.5;1"
             keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
             repeatCount="indefinite"/>
    <!-- Opacity pulses gently -->
    <animate attributeName="opacity" values="0.8;0.3;0.8"
             dur="4s" calcMode="spline"
             keyTimes="0;0.5;1"
             keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
             repeatCount="indefinite"/>
    <!-- Stroke thins as it expands -->
    <animate attributeName="stroke-width" values="3;1;3"
             dur="4s" repeatCount="indefinite"/>
  </circle>
</svg>

A "breathing" circle: radius, opacity, and stroke-width all animate with eased timing. Feels organic because of the spline easing - compare to a linear version and the difference is dramatic.

14. Practical Example: Loading Spinner

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="40" fill="none"
          stroke="#61afef" stroke-width="6"
          stroke-dasharray="200" stroke-dashoffset="200"
          stroke-linecap="round">
    <!-- Draw the stroke -->
    <animate attributeName="stroke-dashoffset"
             values="200;0;200" dur="2s"
             repeatCount="indefinite"/>
  </circle>

  <!-- Rotate the whole circle for spin -->
  <animateTransform attributeName="transform" type="rotate"
    values="0 50 50;360 50 50" dur="1.5s"
    repeatCount="indefinite"
    xlink:href="circle"/>
</svg>

A loading spinner combining stroke-dashoffset animation (the drawing effect) with rotation. Two animations, zero JavaScript. This uses <animateTransform> which we'll cover fully in Lesson 10.

15. Browser Support & the "Deprecated" Myth

You may have read that SMIL is "deprecated." Here's the actual situation:

SMIL is not deprecated in any current browser. The SVG 1.1 spec defines it, and the SVG Animations Level 2 spec is actively developed. For self-contained SVG animations, SMIL remains the best tool.

Accessibility consideration

Respect prefers-reduced-motion. While SMIL doesn't directly respond to this media query (unlike CSS animations), you can use a <style> block to hide or pause animated elements: @media (prefers-reduced-motion: reduce) { animate, set, animateTransform, animateMotion { display: none; } }

16. SMIL vs CSS vs JavaScript - When to Use What

The browser has three completely independent animation systems. They're not wrappers around each other - each has its own spec, its own engine, and its own strengths:

SMILCSS AnimationsJavaScript
Lives inSVG markup (XML elements)Stylesheets / <style><script> blocks
TargetsAny SVG attribute (geometry, paint, filters, path data, viewBox)CSS properties onlyAnything via DOM
Self-containedYes - no dependenciesNeeds a <style> blockNeeds a <script> block
OrchestrationNative syncbase timing (anim1.end+0.5s)Manual delay chainingFull programmatic control
EasingLinear, discrete, paced, splineLinear, ease, cubic-bezier, stepsYou calculate it
PerformanceCPU (paint step)GPU compositor (for opacity/transform)Depends on implementation
Path morphingNative (d attribute)Limited (some browsers support via CSS d)Full control with libraries
Event triggersBuilt-in (begin="click")Requires JS to toggle classesNative

Decision guide

Use caseBest toolWhy
Self-contained SVG file (no external deps)SMILNo <style> or <script> needed - works in <img> tags
Hover/focus effects on inline SVGCSSCSS transitions are simpler and GPU-accelerated for opacity/transform
Animate d (path morphing)SMILNative support; CSS d property support is inconsistent
Animate viewBox (zoom/pan)SMILOnly SMIL can animate this declaratively
Complex choreography with user interactionJavaScriptConditional logic, state machines, physics
Performance-critical (60fps transform/opacity)CSSGPU compositor layer - doesn't repaint
Sequenced animation chainSMILSyncbase timing makes this trivial declaratively
Data-driven or generative animationJavaScriptNeed loops, conditionals, API data
SVG inside <img> tagSMILOnly option - <img> blocks scripts and external CSS
They're not mutually exclusive

You can (and often will) mix them. Use SMIL for the base animation loop, CSS for hover states and transitions, and JavaScript for user-driven interactivity. They compose well because they operate on different layers of the rendering pipeline.

Performance differences

The key performance distinction is where the animation runs in the rendering pipeline:

ApproachWhere it runsCost per frame
CSS transform / opacityGPU compositor threadNearly free - no repaint
CSS other properties (fill, stroke, etc.)CPU repaintModerate
SMIL (any attribute)CPU repaintModerate
SMIL on filter params (stdDeviation, etc.)CPU repaint + re-rasterise filterExpensive
JavaScript + rAF manipulating DOMCPU + potential layout thrashVaries widely

CSS transform and opacity are special - the browser promotes the element to its own compositor layer (a GPU texture) and animates it without touching the main thread. No repaint, no relayout. That's why they hit smooth 60fps even on a busy page. Everything else - including SMIL and CSS animating fill/stroke - triggers a CPU repaint each frame.

When does this matter?

For the kind of self-contained SVG animations you're building (5–15 elements, moderate complexity), SMIL performance is not a concern. It becomes relevant with 30+ simultaneous animations, high-frequency cycling, animated filters (which re-run the entire filter pipeline every frame), or mobile devices with weak CPUs. For anything that maps to transform or opacity, prefer CSS for the GPU fast-path. Use SMIL for everything else.

Quiz

Question 1

What does fill="freeze" on an <animate> element do?

Sets the fill colour of the parent element to frozen blue
Holds the final animated value after the animation ends
Pauses the animation at its current frame indefinitely
Prevents the animation from being overridden by CSS

Question 2

What is the difference between calcMode="linear" and calcMode="paced" when animating through the values "0;100;300"?

Linear uses cubic easing; paced uses no easing at all
Linear plays in order; paced randomises the value sequence
Linear spends equal time on each segment; paced maintains constant velocity across all segments
Linear requires keyTimes; paced requires keySplines values

Question 3

How do you make a SMIL animation start when the user clicks the animated element?

Set trigger="onclick" on the animate element
Set begin="click" on the animate element
Wrap the animate in an <event type="click"> element
Add a JavaScript click handler that calls animate.start()

Question 4

What constraint must be met to animate the d attribute (path morphing)?

Both paths must have the same total length in user units
Both paths must use only cubic bezier commands (C/c)
Both path strings must have the same number and type of commands
The paths must be defined in the same defs block

Question 5

What does accumulate="sum" do on a repeating animation?

Adds all animated values together into a single final value
Each repeat cycle starts from where the previous cycle ended
Sums the animation with the element's base CSS value
Adds a cumulative delay between each repeat cycle

Exercises

  1. Pulsing dot - Create a circle that pulses its radius and opacity in a breathing pattern using calcMode="spline" for natural easing. Make it feel alive, not mechanical.
  2. Colour cycle - Animate a rectangle's fill through 5 colours of your choice over 10 seconds, looping indefinitely.
  3. Progress bar - A horizontal rect whose width animates from 0 to full width over 3 seconds, then freezes. Use fill="freeze".
  4. Bouncing ball - Animate a circle's cy with values and keySplines to create a realistic bounce (fast at bottom, slow at top). Hint: use ease-in on the way down and ease-out on the way up.
  5. Polyrhythm - Put 3 animations on one element with durations 3s, 4s, and 5s. Observe how long it takes before the pattern repeats exactly.