Для контекста: я Flutter-инженер и техлид, последние годы работаю с production-приложениями на Flutter — мобильными, web и гибридными. В моей практике были fintech, маркетплейсы, food delivery и iGaming-продукты, где к UI обычно предъявляют довольно жёсткие требования: сложная графика, анимации, дизайн-системы, производительность, стабильность и предсказуемая доставка фич.

full_flutter_svg (пришлось рисовать через ai из меня плохой художник)
full_flutter_svg (пришлось рисовать через ai из меня плохой художник)

Почему animated SVG не работает во Flutter «как в браузере» — и как я попытался это исправить

Веб-команда отдаёт SVG.
В Chrome он работает.
В Safari — тоже.
Во Flutter — внезапно превращается в статичную картинку, частично ломается или требует обходных путей вроде GIF, видео, Lottie или ручной переписи анимации.

Я попробовал решить именно этот класс проблем и в итоге написал пакет full_svg_flutter — SVG-рендерер для Flutter, который старается приблизить поведение SVG к тому, как его ожидаешь увидеть в браузере.

В статье расскажу:

  • почему animated SVG — это не “просто нарисовать path”;

  • какие подсистемы приходится реализовывать;

  • где такой пакет реально полезен;

  • где он, наоборот, не нужен;

  • и почему в какой-то момент начинаешь понимать, что внутри делаешь маленький браузер.

Пакет

Откуда вообще берётся проблема

иногда мы даже не видим ничего (даже первого кадра)
иногда мы даже не видим ничего (даже первого кадра)

Если говорить про обычные статичные SVG, экосистема Flutter давно уже закрывает много задач достаточно хорошо.

Но как только речь заходит о реальных SVG из production, быстро выясняется, что «поддержка SVG» и «поддержка SVG примерно как в браузере» — это совсем разные уровни.

Типичный сценарий выглядит так:

  1. дизайнер или веб-команда отдаёт SVG;

  2. этот SVG уже работает на web;

  3. Внутри такого SVG может быть не только геометрия, но и анимации, CSS, ссылки по id, маски, фильтры, текст, вложенные трансформации и зависимости между элементами.

  4. потом этот же файл хотят использовать во Flutter.

И вот здесь начинается самое интересное.

Потому что файл, который кажется “обычным SVG”, на деле может содержать довольно сложное поведение, которое в браузере давно воспринимается как само собой разумеющееся.

Что я хотел получить в итоге

Снаружи задача для разработчика должна выглядеть максимально просто:

FSvgPicture.asset('assets/animated.svg')

Идея в том, что человеку не должно быть нужно думать о том, как работает svg в деталях и различные его особенности, а в идеале — передал SVG и получил поведение, максимально близкое к ожидаемому в браузере.

Сразу важная оговорка: это не «замена вообще всему»

Я специально хочу проговорить это в начале, потому что иначе статья легко скатывается в маркетинг.

full_svg_flutter — это не универсальная серебряная пуля.

Если коротко:

  • flutter_svg — отлично подходит для большого количества статичных SVG-сценариев;

  • Lottie — отлично подходит, если source of truth у команды именно Lottie;

  • Rive — отлично подходит, если анимация сделана в Rive;

  • PNG/WebP — иногда вообще лучший ответ, если задача чисто декоративная и интерактивность не нужна.

Мой use case другой:

SVG уже существует как исходный артефакт. Он работает в браузере. И хочется, чтобы именно этот SVG работал во Flutter без конвертации в GIF, видео или другой формат.

Почему animated SVG — это сильно сложнее, чем кажется

На первый взгляд кажется: ну что там, SVG - это же просто вектор.

Но как только начинаешь смотреть на реальные файлы, становится ясно, что SVG — это не просто «нарисовать набор path‑ов».

Это сразу несколько слоёв:

  • документная структура;

  • стили и наследование;

  • ссылки между элементами;

  • система координат;

  • таймлайн анимаций;

  • трансформации;

  • фильтры;

  • текст;

  • порядок отрисовки;

  • частичное обновление дерева;

  • кеширование.

То есть по сути ты имеешь дело не с картинкой, а с чем-то между:

  • DOM,

  • style engine,

  • animation engine,

  • render tree.

И как только начинаются реальные SVG-анимации, это становится очень заметно.

Какие фичи реальных SVG быстрее всего ломают «простое решение»

Вот список того, что чаще всего делает задачу нетривиальной:

  • <animate>

  • <animateTransform>

  • <animateMotion>

  • CSS @keyframes

  • path morphing

  • animated opacity, fill, stroke, transform

  • masks / clipPath

  • filters

  • gradients

  • defs / use

  • nested groups

  • text, tspan, textPath

  • presentation attributes + inline style + CSS inside <style>

  • inheritance / cascade

Проблема в том, что все эти вещи не живут изолированно.

Например:

  • на группу может быть навешан transform;

  • внутри неё — mask;

  • внутри mask — ещё один shape;

  • у shape цвет задан через CSS-класс;

  • сам класс анимируется через keyframes;

  • а поверх ещё применён filter.

То есть “поддержать одну фичу” часто означает, что надо уже иметь пол-нормальной внутренней модели документа.

Что пришлось реализовать внутри

1. SVG DOM

Первое, без чего всё быстро разваливается, — это внутренний DOM.

Нужно распарсить XML в набор объектов, а иметь структуру, где каждый элемент знает:

  • свои атрибуты;

  • детей;

  • родителя;

  • id, class;

  • computed style;

  • transform;

  • paint properties;

  • visibility / display;

  • связи с defs, use, clipPath, mask, gradients и filters.

Без этого сложно корректно поддержать даже базовые зависимости между элементами, не говоря уже об анимации.

2. Style resolution и каскад

Одна из самых коварных частей SVG — стили могут приходить отовсюду.

Например, один и тот же fill может быть задан так:

<path fill="red" />

или так:

<path style="fill:red;" />

или так:

<style>
  .logo-shape {
    fill: red;
  }
</style>
<path class="logo-shape" />

И всё это ещё может наследоваться.

Если не построить слой computed styles, очень быстро начинаются странные рассинхроны: в одном SVG что-то красится не тем цветом, в другом ломается stroke, в третьем не применяются классы.

3. Таймлайн анимаций

Как только появляется SMIL или CSS-анимация, нужна система времени.

То есть недостаточно “на старте прочитать параметры анимации”.

Нужно уметь:

  • определять текущий момент времени;

  • вычислять активность анимации;

  • поддерживать begin, dur, repeatCount, repeatDur, fill;

  • интерполировать значения;

  • учитывать easing / splines;

  • применять результат к целевому атрибуту;

  • корректно инвалидировать render state.

И всё это должно быть синхронизировано с frame lifecycle Flutter.

4. Path morphing

Path morphing — один из самых интересных и болезненных случаев.

Если очень упростить, то мало просто взять две строки с d="" и попробовать “смешать” числа.

Чтобы morphing был корректным, нужно:

  • распарсить path data;

  • нормализовать команды;

  • привести относительные и абсолютные команды к совместимому виду;

  • убедиться, что структуры path-ов сопоставимы;

  • интерполировать параметры команд;

  • корректно собирать результат обратно.

На демо-файлах это можно “почти сделать” быстро.
На реальных SVG — начинается веселье.

5. Filters

SVG-фильтры — это отдельный мир.

Даже если брать относительно базовые вещи вроде blur, color matrix, blend/composite или drop shadow, быстро выясняется, что вопрос не только в поддержке операций, но и в том:

  • как считать bounds;

  • как не рисовать слишком много;

  • как кешировать промежуточный результат;

  • как пересчитывать только то, что действительно изменилось;

  • как не убить производительность на анимациях.

Фильтры — одно из первых мест, где архитектурные компромиссы начинают ощущаться очень явно.

6. Performance и кеширование

Со статикой всё относительно приятно: можно много кешировать.

С анимированными SVG всё сложнее:

  • часть дерева может быть статичной;

  • часть — меняться каждый frame;

  • часть зависит от стилей;

  • часть — от filter chain;

  • часть — от transform state;

  • часть — от timeline.

Из-за этого пришлось думать в нескольких слоях:

  • parse cache;

  • DOM cache;

  • computed style cache;

  • picture cache;

  • raster cache;

  • частичная invalidation.

И очень быстро пришёл к выводу, что для такого пакета бессмысленно смотреть только на “средний FPS”.

Гораздо важнее:

  • cold parse time;

  • warm render time;

  • average frame time;

  • p90 / p99;

  • worst frame;

  • jank frames;

  • memory delta.

Потому что пользователь замечает не “среднюю температуру по больнице”, а лаги и микрофризы.

Архитектура в двух словах

Если очень грубо, внутренний пайплайн выглядит так:

  1. SVG XML

  2. Parser

  3. SVG DOM

  4. Style resolver / computed styles

  5. Animation engine / timeline

  6. Render tree

  7. Painter / canvas layer

  8. Cache layer

  9. Flutter widget API

Это, конечно, упрощение, но именно такая ментальная модель помогает объяснить, почему задача внезапно оказывается намного шире, чем просто “отрисовать вектор”.

Как это выглядит снаружи

Снаружи хотелось сделать API максимально узнаваемым для Flutter-разработчика.

Например:

FSvgPicture.asset('assets/animated.svg')

Идея в том, чтобы migration path был максимально простым и не требовал “учить новый мир” просто ради того, чтобы получить анимированный SVG.

Где такой пакет реально полезен

Я вижу несколько основных сценариев.

1. Source of truth — именно SVG

Это главный случай.

То есть команда не хочет экспортировать SVG в другой формат, а хочет использовать один и тот же артефакт между web и mobile.

2. Animated SVG из дизайна или веба

Когда уже есть готовые SVG, которые ведут себя в браузере как нужно, и хочется приблизить это поведение во Flutter.

3. SVG-heavy UI

Например:

  • промо-экраны;

  • dashboard-like интерфейсы;

  • игровые / iGaming-подобные интерфейсы;

  • интерактивные иллюстрации;

  • сложные векторные элементы.

4. Edge cases, которые плохо укладываются в «просто иконки»

Masks, filters, clipping, text, nested transforms, animation combinations — всё это как раз тот класс задач, ради которого вообще имеет смысл заморачиваться отдельным renderer.

Что оказалось самым неожиданным

Наверное, самое интересное ощущение было таким:

чем дальше продвигаешься, тем меньше это похоже на “библиотеку для SVG” и тем больше — на набор маленьких подсистем, которые в браузере воспринимаются как данность.

Например:

  • пока не реализуешь styles — часть SVG выглядит странно;

  • пока не сделаешь transform composition — начинают плыть группы;

  • пока не тронешь filters — кажется, что уже почти всё готово;

  • как только приходят реальные production SVG, вскрываются все слабые места.

И это, пожалуй, главное, что мне дала работа над пакетом: гораздо больше уважения к тому, насколько много логики браузеры прячут под “ну это просто SVG”.


Что сейчас поддерживается

На текущем этапе я сфокусировался на следующем наборе возможностей:

  • static SVG rendering;

  • SMIL animations;

  • CSS animations / keyframes;

  • animated transforms;

  • path morphing;

  • gradients;

  • masks and clipping;

  • filters;

  • text rendering;

  • hit testing;

  • accessibility support;

  • familiar SvgPicture-style API.

Пакет:
https://pub.dev/packages/full_svg_flutter


Что хочу сделать дальше

Сейчас мне особенно интересны две вещи.

1. Реальные edge cases

Я хочу прогонять пакет не только на красивых demo-assets, но и на реальных SVG, которые:

  • работают в Chrome/Safari;

  • становятся статичными во Flutter;

  • используют SMIL или CSS animation;

  • используют mask, clipPath, filter;

  • содержат textPath;

  • ломаются на стыке нескольких фич.

Если у вас есть такие примеры — буду очень рад, если вы скинете их в issues или в комментарии или на почту denis.nadey@gmail.com или telegram @denisdandy


2. Публичный benchmark suite

Я хочу отдельно собрать воспроизводимый benchmark-набор, чтобы смотреть не на абстрактное “быстро/медленно”, а на нормальные метрики:

  • cold parse time;

  • warm render time;

  • dense list/grid scenarios;

  • scroll stress tests;

  • animation frame stability;

  • p90 / p99 frame timing;

  • jank frames;

  • memory delta.

Именно такие цифры дают нормальное представление о том, насколько жизнеспособен renderer на практике.


Итоги

Если коротко, то главный вывод для меня такой:

animated SVG — это не просто картинка, а целый набор подсистем, которые в браузере уже давно живут вместе: DOM, styles, timeline, transforms, filters, render tree.

Именно поэтому “поддержка SVG” и “поддержка SVG примерно как в браузере” — это задачи совершенно разной сложности.

full_svg_flutter — моя попытка приблизить второе во Flutter.

Если у вас есть сложные SVG, которые работают в браузере, но ломаются или упрощаются во Flutter — присылайте.
Такие кейсы для меня сейчас ценнее любой синтетики.

Ссылки: