There are four ways to get a morph onto a page, and this manual calls each one a . They differ in what they cost and in what they can do, and the browser matrix decides more than the size does.
By the end of this page you will be able to pick one for something you are shipping, and know before you test it which engines animate it and which show a still icon. One of the four is static in Safari, and it is the one this manual reaches for first everywhere else.
They are named rather than numbered, and that is a correction. They were tier 0, tier 1, tier 1.5 and tier 2 for a long time, and the half was not a joke: SMIL was added late and slotted in where it belonged in the argument rather than where a whole number was free. A numbering that records the order things were written in is worse than no numbering, and the names were already doing the work.
| Tier | Size | Chromium 151 | Safari 26.3 |
|---|---|---|---|
| compiled CSS | 0 KB | animates | static icon |
| SMIL | 0 KB | animates | animates |
| the driver | 2.00 kB | animates | animates |
| the React binding | 2.82 kB | animates | animates |
Best when the morph is a toggle and you want nothing on the wire.
A two-keyframe morph compiles to a CSS transition, which runs both directions and
interrupts cleanly. Anything sampled compiles to @keyframes, and an animation plays one
direction only, so the way back cuts rather than animating. CSS cannot distinguish a state
flip from a first paint, so a reverse animation on the resting state would fire on load.
The same baked keyframes can also be handed to element.animate(), which is the Web
Animations API. This had a tier of its own for a while and has been demoted to a note,
because it is only ever the answer on an engine that lacks CSS d but has WAAPI d, and no
engine measured here is in that set. It is listed so that somebody who finds one knows the
compiled output already fits, not because it is a route worth planning for.
The one this manual left out for too long, and the only other route to zero JavaScript.
<animate attributeName="d"> is part of the SVG spec rather than CSS, which changes three
things at once. It animates any attribute, not the handful CSS recognises, so points
and viewBox are reachable where CSS cannot touch them. It needs no stylesheet, so it
survives being loaded through <img> where an external SVG gets no CSS at all. And it is
not blocked by the missing CSS d property, which is exactly the hole compiled CSS falls into.
This row used to carry a warning saying it had not been measured. It has now:
pnpm measure:smil pauses the SVG timeline, steps it to an exact moment and reads the
geometry back off the live element, in both engines. d animates and interpolates linearly
in each, and the midpoint lands on 12.00 where it should.
The run also found something the widely-reported version of this does not mention.
That disagreement matters more than it looks. is still yours to decide, which is why Part 3's two answers survive intact, but you cannot lean on the browser to refuse a pairing you got wrong: one engine will refuse it and another will animate something. SMIL is a different way to deliver a morph, not a different way to plan one, and the this manual compiles feeds it as readily as it feeds a stylesheet.
Chained beginEvent/endEvent sequencing is separately reported to be patchy in Safari,
and that part is still repeated rather than measured.
What to reach for by default if you need anything interactive. The is the small piece of JavaScript that plays a compiled morph.
It writes the d attribute directly, so it depends on neither CSS d nor WAAPI and works
everywhere. It adds interruption, real bidirectionality for sampled morphs, velocity
preserving interrupts, and seek for scrubbing.
A morph is a track from A to B, so progress 0 is A and 1 is B. Asking to go back to A mid-flight is not a new animation needing a re-plan, it is the same tween aiming at 0 with its velocity intact.
The same pair, compiled to CSS, cannot do that:
9 keyframes, so it cannot be a CSS transition. It animates out and cuts back. The driver handles this pair in both directions for 2.00 kB.
Worth stating plainly, because "use CSS, it runs on the GPU" is the most repeated advice in web animation and none of it reaches a morph.
A couple of properties, transform and opacity, can be animated by a separate thread
while the main one is busy. That is the whole of what "hardware accelerated" means, and it
is why a CSS transform stays smooth through a long task that a JavaScript one stutters
through.
The browser renders in three phases, layout then paint then composite, and changing a
property re-runs its own phase and every phase after it. transform and opacity land
only in the last one, so the compositor thread can carry them alone.
Nothing about d works that way. Animating geometry re-runs paint on every frame, on the
main thread, whichever tier is driving it.
d is not in that set and cannot join it. Changing geometry changes what has to be
rasterised, so every frame of every morph repaints, on the main thread, whichever tier drove
it. Compiling to CSS buys you nothing on the wire. It does not buy you a free thread.
The floor is at least paint rather than layout, which is a genuine mercy: SVG has no layout
algorithm, and one shape's coordinates cannot displace another's the way a wider div shoves
its siblings. viewBox is the only attribute that reaches back that far.
What it does change is what you put next to a morph. A rotation or a fade riding
alongside one should be a transform or an opacity rather than more sampled geometry,
because those two can leave the main thread even while the d underneath them cannot.
This is a second reason for the check in rotation. Sampling a rotation into keyframes does not only cost bytes. It takes motion that a compositor could have carried for free and moves it into the one phase that has to run on the main thread.
Compile to CSS for a toggle that never needs to reverse mid-flight, and use the driver for everything else. Mixing them on one page is fine, since they consume the same compiled output.
SMIL is the one to reach for when the constraint is no stylesheet, either because the SVG ships as a standalone file or because Safari has to animate and shipping a driver is not on the table. It is not the default here only because interruption and scrubbing are what most interactive morphs need, and those are the driver's.
What actually animates where, including the one browser that refuses.
You will need it in Part 9.