SMIL in SVG: Animating What CSS Can’t Touch
CSS animations cover a lot of ground, but some SVG properties simply can’t be animated with CSS. That’s where SMIL (Synchronized Multimedia Integration Language) steps in. Introduced by the W3C in 1998 for multimedia synchronization, SMIL was built into SVG 1.1 and remains usable today. Google reversed its decision to deprecate the technology years ago, so it’s still a solid option for adding simple, semantic animations to SVG designs.
The basic animation toolkit is small: move elements, rotate them, scale them, and change their colour or opacity. These techniques work for everything from shifting a character across the screen to simulating the swing of a bear descending under a parachute. But SMIL goes beyond CSS by letting you animate SVG-specific attributes directly.
Animations can be placed inside any SVG element, including primitive shapes like circles and rectangles, or grouped into paths and polygons:
<circle ...>
<animate>...</animate>
</circle>
Alternatively, you can define animations elsewhere in the SVG and hook them to elements with an xlink attribute:
<g id="yogi">...</g>
...
<animate xlink:href="#yogi">…</animate>
Building an Animation with <animate>
The <animate> element is one of several animation tools SVG offers. Paired with an attributeName, it animates a specific property of an element.
<circle
r="50"
cx="50"
cy="50"
fill="#062326"
opacity="1"
/>
To move a circle horizontally, set attributeName to cx, the x-axis centre point:
<circle ... >
<animate attributename="cx"></animate>
</circle>
That alone does nothing. You also need from for the starting position, to for the ending position, and dur for how long the transition takes:
<circle ... >
<animate
attributename="cx"
from="50"
to="500"
dur="1s">
</animate>
</circle>
For more precise control, replace from and to with a semicolon-separated list of values:
<circle ... >
<animate
attributename="cx"
values="50; 250; 500; 250;"
dur="1s">
</animate>
</circle>
Finally, repeatcount controls how many times the animation runs, and repeatdur sets how long the repetition should last:
<circle ... >
<animate
attributename="cx"
values="50; 250; 500; 250;"
dur="1s"
repeatcount="indefinite"
repeatdur="180s">
</circle>
Most SVG elements have animatable attributes. Consider a title card from the 1959 “Brainy Bear” episode, where Yogi’s head sits under a dome radiating energy. The SVG includes three path elements with opacity, stroke, and stroke-width attributes — all of which can be animated:
<path opacity="1" stroke="#fff" stroke-width="5" ... />
Animating each path’s opacity from 1 to .5 and back creates a pulse effect:
<path opacity="1" ... >
<animate
attributename="opacity"
values="1; .25; 1;"
dur="1s"
repeatcount="indefinite">
</animate>
</path>
Staggering each path’s begin value makes the energy radiate outward from Yogi:
<path ... >
<animate begin="0" … >
</path>
<path ... >
<animate begin=".5s" … >
</path>
<path ... >
<animate begin="1s" … >
</path>
Try it yourself:
See the Pen [Brainy Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/qEEzYgG) by Andy Clarke.
For more natural movement, add multiple animate elements to the same target. The paths also carry a stroke-width attribute, so animate it between 5 and 7:
<path ... >
<animate attributename="opacity" ... ></animate>
<animate attributename="stroke-width" ... ></animate>
</path>
Finally, animate the dome’s fill between two colours over five seconds to suggest the machine heating up:
<path fill="#50D9E0" ... >
<animate
attributename="fill"
values="#50D9E0; #E18C50;"
dur="5s"
begin="2s"
>
</path>
When the animation finishes, the dome returns to its original state. To freeze the final colour, add the fill property with a value of freeze:
<path fill="#50D9E0" ... >
<animate fill="freeze">
</path>
Transformations with animateTransform
Where <animate> handles attributes, animateTransform handles transformations: rotations, scaling, skewing, and translations. Start by specifying a transform property like translate:
<path transform="translate(0,0)"/>
Then animate it just like <animate>, adding an attributename and setting the transform type:
<animatetransform
attributename="transform"
type="rotate">
</animatetransform>
- Scale uses
xandyvalues (.5,1). - Rotate uses degrees (
0–360) plus optionalxandy(360,0,0). - Translate uses
xandyvalues (50,100). - Skew uses
xandyvalues too (50,100).
A useful feature: these values can add to an element’s existing transformation values rather than replace them. If an element already has a translate value of 100, 0:
<path transform="translate(100, 0)"/>
Animating that translation horizontally by 100:
<animatetransform
attributename="transform"
type="translate"
from="0, 0"
to="100, 0"
additive="sum">
</animatetransform>
With the additive property set to sum, the animation values are relative to the original — starting at 100 and ending at 200.
Similarly, accumulate set to sum makes each repetition build on the last. Translated by 100 and repeated five times, the element moves by a total of 500:
<animatetransform
attributename="transform"
type="translate"
from="0, 0"
to="100, 0"
additive="sum"
accumulate="sum"
/>
For the 1958 “Big Break” title card, Yogi drifts downward under a parachute. That requires two transform animations: translate and rotate. First, animate the group containing Yogi and his chute from an initial vertical position of 1200 off the top of the viewBox, translating his descent to 1000 over 15 seconds:
<g transform="translate(1200, -1200)">
...
<animateTransform
attributeName="transform"
type="translate"
values="500,-1200; 500,1000"
dur="15s"
repeatCount="1"
/>
</g>
The movement looks stiff. Add a second animatetransform element — an indefinitely repeating +/- 5-degree rotation — to swing Yogi side to side during the fall:
<animateTransform
attributeName="transform"
type="rotate"
values="-5; 5; -5"
dur="14s"
repeatCount="indefinite"
additive="sum"
/>
Try it yourself:
See the Pen [Big Break SVG animation [forked]](https://codepen.io/smashingmag/pen/PwwraNm) by Andy Clarke.
Controlling Start Times
By default, animations begin as soon as the page loads. The begin property changes that, allowing delays or interaction-triggered starts.
In “Robin Hood Yogi” from 1959, Yogi shoots an arrow into an apple on Boo-Boo’s head:
The arrow flies immediately when the page loads. Add anticipation by delaying the start by two seconds:
<animatetransform
attributename="transform"
type="translate"
from="0 0"
to="750 0"
dur=".25s"
begin="2s"
fill="freeze"
/>
Or let the viewer trigger the shot by clicking the arrow:
<animatetransform
...
begin="click"
/>
Combine a click event with a delay — no JavaScript needed:
<animatetransform
...
begin="click + .5s"
/>
Click the arrow to try it:
See the Pen [Robin Hood Yogi CSS animation [forked]](https://codepen.io/smashingmag/pen/OPPeERj) by Andy Clarke.
Synchronising Multiple Animations
Named animations can be coordinated with each other. In the 1958 “Pie-Pirates” episode, Yogi tries to steal a pie while outwitting a bulldog. The title card shows the chase in progress, with two groups of paths — one for Yogi, one for the dog — both translated off the left edge of the viewBox:
<g class="dog" transform="translate(-1000, 0)">
...
</g>
<g class="yogi" transform="translate(-1000, 0)">
...
</g>
An animatetransform on each group brings them back into view:
<!-- yogi -->
<animateTransform
attributeName="transform"
type="translate"
from="-1000,0"
to="0,0"
dur="2s"
fill="freeze"
/>
<!-- dog -->
<animateTransform
attributeName="transform"
type="translate"
from="-1000,0"
to="0,0"
dur=".5s"
fill="freeze"
/>
That alone feels flat. Add a second pair of animations to bounce both characters:
<!-- yogi -->
<animateTransform
attributeName="transform"
type="rotate"
values="-1,0,450; 1,0,450; -1,0,450"
dur=".25s"
repeatCount="indefinite"
/>
<!-- dog -->
<animateTransform
attributeName="transform"
type="rotate"
values="-1,0,450; 1,0,450; -1,0,450"
dur="0.5s"
repeatCount="indefinite"
/>
For better pacing, let Yogi enter first, pause, then bring in the other motion. Give Yogi’s translate animation an ID:
<animateTransform
id="yogi"
type="translate"
...
/>
Watch out: Firefox won’t begin animations with an ID when the ID contains a hyphen. Replace hyphens with underscores to work around it.
Then set his rotate animation to begin half a second after the #yogi animation ends:
<animateTransform
type="rotate"
begin="yogi.end + .5s"
...
/>
You can chain timing sequences with begin referencing where named animations start or end. The bulldog enters two seconds after Yogi begins his entrance:
<animateTransform
id="dog"
type="translate"
begin="yogi.begin + 2s"
fill="freeze"
...
/>
One second after the dog catches up, a rotate transform makes him bounce:
<animateTransform
type="rotate"
...
begin="dog.begin + 1s"
repeatCount="indefinite"
/>
The background rectangles whizzing past synchronise to one second before the bulldog ends his run:
<rect ...>
<animateTransform
begin="dog.end + -1s"
/>
</rect>
Try it yourself:
See the Pen [Pie-Pirates SVG animation [forked]](https://codepen.io/smashingmag/pen/LEEKryp) by Andy Clarke.
The background movement is timed relative to the dog’s arrival, which itself is relative to Yogi’s arrival. The result is a sequence where every animation feels connected, built entirely from SMIL’s The third way SMIL can be applied in SVG is with the To make an element follow a route, you specify the motion path with the Alternatively, the path itself can be defined as a separate reusable element. Placing it inside a Finding the right trajectory took testing a few different paths — one felt too bouncy, another too flat, but a third gave the desired movement shape. Refining the pacing was done with the The net effect is that Yogi rushes through the first three points, lingers as he crosses the text, and then speeds up again before he exits the SMIL’s combination of coordinate transforms, motion paths, and synchronized timelines keeps it relevant for SVG animation. It adds life to designs without pulling in a framework or relying on JavaScript, workable inside compact SVGs that can be embedded anywhere. The Although it’s not the newest tool in the box — and has sometimes been called outdated — SMIL remains a legitimate choice for simple, semantic animation. Nearly a decade ago, Google reversed an earlier decision to deprecate the feature, and it has stayed available since. If the code fits your use case, don’t be put off by claims that it’s “dead”: it’s still very much alive.
begin property.
Moving Elements Along Complex Paths
animatemotion element, which lets elements travel along an arbitrary path rather than simple vector moves. While it accepts the same properties that animate and animateTransform do, it adds options for direction and timing that are unique to path-based movement.path property, which uses the same d coordinate data as a standard SVG path. In the example of the 1959 Yogi Bear cartoon “The Runaway Bear,” the character was meant to leap in and out of the screen at a variable speed, accelerating on entry and exit while slowing down as he crossed the title text.defs block keeps it from rendering on screen, and it can then be referenced inside animateMotion using a nested mpath element:keyPoints property, which defines specific points along the path (between 0 and 1) and lets you control how much time the element spends between them. With five key points defined, a matching set of keyTimes values distributed the two-second duration unevenly across the route:viewBox. The full result is something you can try interactively, like the example below:Why SMIL Still Has a Place
begin attribute also simplifies chaining one animation to the next in a way that is often more straightforward than CSS-based alternatives, and because the instructions live inside the SVG itself, SMIL keeps working wherever the file travels.Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool.
[BLOCK_53]



