Older articles warn against SMIL: Chrome 1 announced an intent to deprecate it in 2015, and Internet Explorer and the original Edge never supported it. The deprecation never happened, and since Edge moved to Chromium 4,389 in January 2020 SMIL has been Baseline everywhere. Declarative animation also runs where scripts cannot: in a Chrome 154 test, an <img> ran a <set> and a CSS @keyframes inside its SVG, but not its <script>.
| Technique | What it animates | Runs inside <img> | Control |
|---|---|---|---|
| SMIL elements | Any animatable attribute, including points and d | Yes | Event timing, seeking |
| CSS transitions and animations | CSS properties: fill, stroke-*, transform, r, cx, ... | Yes | Media queries, classes |
| Web Animations API | The same CSS properties | No | Play, pause, seek, reverse |
| requestAnimationFrame loop | Anything, including data-driven geometry | No | Full, and all the code |
Prefer CSS for state changes, where @media (prefers-reduced-motion: reduce) can switch motion off; use SMIL for self-contained animated files and path morphing (CSS d works only in Chromium and Firefox 555 ).
<!doctype html>
<style>
body { margin: 0; font: 13px system-ui, sans-serif; background: #fafaf7; }
svg { width: 100%; max-width: 600px; display: block; }
.panel { fill: #fff; stroke: #d8d4c8; }
.title { font-weight: 700; font-size: 14px; }
.sub { font-size: 11px; fill: #6b665c; }
/* 2. CSS keyframes: animates CSS properties such as transform and fill */
.css-book { transform-box: fill-box; transform-origin: center; animation: tilt 2s ease-in-out infinite alternate; }
@keyframes tilt { from { transform: rotate(-12deg); fill: #e07a5f; } to { transform: rotate(12deg); fill: #3d5a80; } }
@media (prefers-reduced-motion: reduce) { .css-book { animation: none; } }
</style>
<svg viewBox="0 0 600 300">
<!-- 1. SMIL: morphs path data (d) declaratively; also works inside <img> -->
<rect class="panel" x="5" y="5" width="142" height="290" rx="8"/>
<text class="title" x="16" y="28">SMIL</text>
<path fill="#81b29a" d="M30 200 L76 110 L76 110 L122 200 Z">
<animate attributeName="d" dur="2s" repeatCount="indefinite"
values="M30 200 L76 110 L76 110 L122 200 Z; M30 200 L30 110 L122 110 L122 200 Z; M30 200 L76 110 L76 110 L122 200 Z"
calcMode="spline" keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"/>
</path>
<text class="sub" x="16" y="240">any attribute, incl. d</text>
<text class="sub" x="16" y="256">runs inside <img>: yes</text>
<!-- 2. CSS animation -->
<rect class="panel" x="153" y="5" width="142" height="290" rx="8"/>
<text class="title" x="164" y="28">CSS</text>
<rect class="css-book" x="194" y="110" width="60" height="90" rx="4"/>
<text class="sub" x="164" y="240">CSS properties only</text>
<text class="sub" x="164" y="256">runs inside <img>: yes</text>
<!-- 3. Web Animations API -->
<rect class="panel" x="301" y="5" width="142" height="290" rx="8"/>
<text class="title" x="312" y="28">Web Animations</text>
<circle id="waapi" cx="372" cy="155" r="30" fill="#f2cc8f" stroke="#3d5a80" stroke-width="4"/>
<text class="sub" x="312" y="240">play / pause / reverse</text>
<text class="sub" x="312" y="256">click the circle</text>
<!-- 4. requestAnimationFrame -->
<rect class="panel" x="449" y="5" width="146" height="290" rx="8"/>
<text class="title" x="460" y="28">rAF loop</text>
<polyline id="wave" fill="none" stroke="#e07a5f" stroke-width="3"/>
<text class="sub" x="460" y="240">data-driven geometry</text>
<text class="sub" x="460" y="256">full control, all code</text>
</svg>
<script>
// 3. WAAPI: a scripted animation you can control
const circle = document.getElementById('waapi');
const anim = circle.animate(
[{ transform: 'translateY(-40px)' }, { transform: 'translateY(40px)' }],
{ duration: 900, iterations: Infinity, direction: 'alternate', easing: 'ease-in-out' });
circle.addEventListener('click', () =>
anim.playState === 'running' ? anim.pause() : anim.play());
// 4. rAF: recompute the points of a sine wave every frame
const wave = document.getElementById('wave');
function frame(t) {
const pts = [];
for (let x = 0; x <= 120; x += 6) pts.push(`${462 + x},${155 + 35 * Math.sin(x / 18 + t / 300)}`);
wave.setAttribute('points', pts.join(' '));
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>