How to play animated SVG in Flutter (flutter_svg won't)
flutter_svg shows animated SVGs as still pictures. svg_animate plays their SMIL and CSS @keyframes in Flutter, in pure Dart. How it works, and the alternatives.

Short answer: flutter_svg doesn't play SVG animations: it skips SMIL
elements like <animate> and ignores <style>, so the file shows as a still
picture. To play the animation that's already in the SVG, add
svg_animate and use
AnimatedSvgPicture where you'd use SvgPicture. It takes the same arguments:
flutter pub add svg_animateAnimatedSvgPicture.asset('assets/spinner.svg', width: 48)It plays SMIL, CSS @keyframes and CSS motion paths, in pure Dart. The rest of
this post is how it works, the three bugs real files exposed, and how it
compares with Lottie, Rive and the other SVG packages.
Our designer sent me a folder of animated SVGs. A spinner, a progress bar, a few icons. Nice stuff, all of it animated right there in the file with plain CSS and SMIL.
I dropped them into flutter_svg and got static pictures.
I spent an evening convinced I'd broken something. Wrong widget, wrong asset
path, something dumb. Turns out no: flutter_svg just skips <animate>. And it
ignores <style> too, which is worse, because that means a stylesheet-driven
SVG doesn't even render with the right colors, let alone move.
The usual answer is "convert it to Lottie". I didn't want to go back to the designer and ask him to rebuild everything in After Effects when the animation was already sitting in the file he'd sent me. So I wrote svg_animate.
How svg_animate works: a flipbook of static SVGs
flutter_svg draws SVGs by compiling them into a binary format with
vector_graphics_compiler, then replaying that. That compiler is good. I had no
interest in writing a renderer.
So: don't. Take the animated document, work out what every animated attribute is at time t, write those values back into the markup, and hand the result to the existing compiler. Do that once per frame ahead of time, then flip through the compiled frames like a flipbook.
A frame costs exactly what a static SVG costs to draw, because it is a static SVG. You pay at load instead.
AnimatedSvgPicture.asset('assets/spinner.svg', width: 48)Same arguments as SvgPicture. Reuses flutter_svg's SvgTheme and
ColorMapper so nothing in an existing app breaks. If the file has no animation
in it, it renders like a normal SvgPicture and doesn't even start a ticker.
Two evenings later the spinner was spinning. I felt great about myself for roughly a week.
Bug 1: an SVGator banner that rendered blank
A 450×450 promo banner exported from SVGator. Coins, sparkles, a train, a "BUY BONUS" badge. 90 KB of markup with five WebP images base64'd into it.
It rendered as nothing at all. Not "wrong" — a blank box.
Here's where I want to be honest about how I debugged it, because I got it wrong
first. I built a minimal test case: a tiny SVG with one <image> in it,
referenced through <use>. Nothing drew. I tried the same file through plain
flutter_svg. Nothing drew there either. Great, I thought, images just don't work
on this pipeline, not my problem.
That conclusion was garbage. I'd hand-rolled the test PNG as a base64 string and it was truncated — zlib couldn't even decompress it. I'd proven nothing except that a broken PNG doesn't render.
Generated a real one. Ran it again. Images render fine. <use> pointing at an
image renders too, just at the wrong size.
So back to the real file, this time properly: feed the raw SVG straight into
vector_graphics_compiler, no code of mine involved.
Failed assertion: 'width > 0 && height > 0' (writeDrawImage)It dies in the compiler. Before any animation logic. SvgPicture would have
shown the same blank box.
Bisecting the file by deleting things: strip the 19 <use href="#img">
references and it compiles. The compiler loses an image's declared width and
height when the image is reached through a reference, gets zero, and then
refuses to draw something of zero size — killing the whole picture rather than
that one element.
Fix on my side: expand a <use> that points at an <image> into the image
itself before compiling. Only images — a <use> on a shape is fine, and
copying those would just bloat the document.
The banner rendered. Coins and all.
Bug 2: slow frames and 27.5 MB of memory
Every compiled frame carries the images inside it, because each frame is a self-contained picture. 82% of each 96 KB frame was image payload. At the default 60 fps that's 300 frames and 27.5 MB.
Worse than the memory: changing frame cost 6.8 ms. The frame budget at 60fps is 16.7 ms. That's 40% of it, just to decode, on an M-series Mac. On a mid-range phone it would have blown straight through.
Two fixes, and I'm not proud of the first one.
The image cache. I went reading how vector_graphics decodes images and found this:
final listener = FlutterVectorGraphicsListener(id: loader.hashCode, ...);
// ...
int _createImageKey(int imageId, int format) => Object.hash(_id, imageId, format);It keys Flutter's global image cache by the hash code of the loader you hand it. And mine was:
int get hashCode => Object.hash(identityHashCode(frames), frameIndex);frameIndex in there. Every single frame opened its own namespace in the cache
and re-decoded the same five bitmaps. I had disabled a cache that was sitting
right there, with my own line of code, and then measured the result and blamed
the architecture.
Dropped frameIndex. Frames are still unequal to each other — that's what ==
is for — and two unequal objects sharing a hash code is perfectly legal Dart.
Here it's the whole point. 6.8 ms → 1.4 ms.
The duplicated bytes. For memory I nearly went and wrote a parser for the binary format to find and hoist the image section. Then I realised I didn't need to understand the format at all: consecutive frames of one animation are the same document with a few numbers changed, and the encoder puts everything the picture embeds at the front. So just compare the frames byte by byte and find the longest run they all start with.
On the banner that run is 78,772 bytes. The compiler reports 78,538 bytes of images. It found them, without knowing what it was looking at.
Store that once, keep only the tails, glue a frame back together when it's asked for. 27.5 MB → 5.1 MB, and reassembling costs about 0.1 ms.
I like this one because it doesn't depend on the encoder's layout. If the format ever stops front-loading resources, the shared run just gets shorter and nothing breaks.
Bug 3: a CSS spinner with no 0% keyframe
Last one, and it's the most embarrassing, because it's the most common CSS animation on earth:
@keyframes spin { to { transform: rotate(360deg) } }No 0%. My code filled the missing end from the element's own transform
attribute, and when the element didn't have one, it fell back to... the other
keyframe. So both ends said 360 and the thing sat perfectly still while
everything around it animated.
The missing end has to come from the value the property has outside the
animation — no rotation for a transform, fully opaque for an opacity. With one
wrinkle: you can't substitute an empty transform, because two transform lists
only interpolate if they're built the same way. The stand-in has to keep the
shape: translate(0 0) against a translate, scale(1) against a scale.
What svg_animate doesn't support
Filters. At all. vector_graphics drops <filter> entirely, so drop shadows
and glows just don't happen — the shapes draw, the effect doesn't. Same for
mix-blend-mode: plus-lighter, which is exactly what editors reach for to make
something glow.
Also no d morphing, no <script>, no :hover, no event-based begin. If you
need any of that, full_svg_flutter
covers a lot more of the format. It's a good package. It also bundles a
JavaScript runtime, which is a fair price for what it does and not one I wanted
to pay for a spinner.
Mine adds two pure-Dart packages, both already in flutter_svg's dependency tree.
svg_animate vs Lottie, Rive and other Flutter SVG packages
"Animated SVG" means different things in different packages. Only some of them play the animation that's already written into the file:
| Package | What it animates | Plays the animation already in the SVG | Notes |
|---|---|---|---|
| svg_animate | SMIL, CSS @keyframes, CSS motion paths |
Yes | Pure Dart, on flutter_svg's own compiler; no filters |
| flutter_svg | Nothing: draws a still picture | No | Skips <animate>, ignores <style> |
| lottie | Lottie JSON files | No | The animation has to be rebuilt in After Effects and exported as Lottie |
| rive | .riv files from the Rive editor |
No | The animation has to be rebuilt in Rive |
| anim_svg | SMIL and CSS @keyframes, converted to Lottie at runtime |
Yes, except SVGator's script-driven exports | Renders with the native thorvg engine through FFI |
| full_svg_flutter | Much more of SVG | Yes | Bundles a JavaScript runtime |
| animated_svg | A transition between two SVG images, such as play to pause | No | For icon toggles, not animated files |
| drawing_animation | A line-drawing effect on <path> strokes |
No | Draws paths progressively |
If your designer already animated the file, in SVGator, by hand, or anywhere else that writes SMIL or CSS, use a package from the "Yes" rows. If you're starting from scratch and want a timeline editor, Lottie or Rive is the better tool.
Install svg_animate from pub.dev
SMIL, CSS @keyframes, CSS motion paths (which is how SVGator writes every
movement, so its exports work), play/pause/seek through a controller. It's one
of the projects I maintain.
If you've got an SVG that renders wrong, send it. Every single thing in this post came from someone's file not working.
FAQ
Does flutter_svg support animated SVGs?
No. flutter_svg draws an SVG as a still picture: SMIL elements such as
<animate> are skipped, and <style> blocks are ignored entirely, so CSS
@keyframes don't play and colours set in a stylesheet don't apply either.
How do I play an animated SVG in Flutter?
Add svg_animate with flutter pub add svg_animate and use
AnimatedSvgPicture where you would use SvgPicture. It takes the same
arguments:
AnimatedSvgPicture.asset('assets/spinner.svg', width: 48)It plays the SMIL and CSS keyframe animations in the file, and an
AnimatedSvgController adds play, pause and seek. A file with no animation in
it renders like a normal SvgPicture.
Do I need Lottie to animate an SVG in Flutter?
Not when the animation is already in the SVG. Files written with SMIL or CSS
@keyframes, including exports from SVGator, play as they are with
svg_animate, with no round trip through After Effects.
What's the difference between svg_animate and animated_svg?
They solve different problems. svg_animate plays the animation inside an SVG
file: SMIL elements and CSS @keyframes. animated_svg doesn't read animation
from the file; it transitions between two separate SVG images, such as a play
icon turning into a pause icon.
Does svg_animate work with SVGator exports?
Yes, with SVGator's CSS export: SVGator writes movement as CSS motion paths,
which svg_animate plays, and the image-heavy banner in this post is an SVGator
export. SVGator's JavaScript export depends on an embedded script, and
svg_animate doesn't run <script>.
Which SVG animation features does svg_animate support?
SMIL (<animate>, <animateTransform>, <animateMotion> and <set>), CSS
@keyframes and CSS motion paths. It interpolates numbers, colours, lengths
and transform lists, and it loads SVGs from assets, the network, files, memory
or a string.
What doesn't svg_animate support?
SVG filters, so no drop shadows or glows; mix-blend-mode: plus-lighter;
morphing a path's d attribute; <script>; :hover; and event-based begin.
For those, full_svg_flutter covers
more of the format, at the cost of bundling a JavaScript runtime.
Is svg_animate fast enough for production?
Every frame is compiled ahead of time and costs the same to draw as a static SVG. On the image-heavy banner in this post, switching frames went from 6.8 ms to 1.4 ms once decoded images were cached, and memory went from 27.5 MB to 5.1 MB once the bytes every frame shares were stored only once.
Which platforms does svg_animate run on?
Android, iOS, macOS, Windows and Linux.
More writing
Aquarium water test log apps for iPhone: 8 compared
Eight iPhone apps for logging aquarium water tests compared: freshwater or reef, test strip scanning, species database, account, and what each costs.
Free parking in Madrid, Barcelona and Valencia: 7 apps compared
Most Spanish parking apps help you pay, not park free. Seven apps compared for Madrid, Barcelona and Valencia, and which one maps free street parking.
Pet health record apps for iPhone: 7 compared
Seven iPhone apps for your pet's vaccinations, vet visits and documents compared: which work without your vet joining, store your own scans, and cost.