- PVSM.RU - https://www.pvsm.ru -
Для контекста: я Flutter-инженер и техлид, последние годы работаю с production-приложениями на Flutter — мобильными, web и гибридными. В моей практике были fintech, маркетплейсы, food delivery и iGaming-продукты, где к UI обычно предъявляют довольно жёсткие требования: сложная графика, анимации, дизайн-системы, производительность, стабильность и предсказуемая доставка фич.
Веб-команда отдаёт SVG. В Chrome он работает. В Safari — тоже.
Во Flutter — внезапно превращается в статичную картинку, частично ломается или требует обходных путей вроде GIF, видео, Lottie или ручной переписи анимации.Я попробовал решить именно этот класс проблем и в итоге написал пакет
full_svg_flutter— SVG-рендерер для Flutter, который старается приблизить поведение SVG к тому, как его ожидаешь увидеть в браузере.В статье расскажу, почему animated SVG — это совсем не “просто нарисовать path”, какие подсистемы пришлось реализовать, где это реально полезно, а где лучше использовать совсем другие инструменты.
В какой-то момент я столкнулся с задачей, которая звучит просто: “взять SVG, который работает в браузере, и показать его во Flutter”. Но оказалось, что если внутри есть SMIL, CSS-анимации, path morphing, masks, filters или сложные стили, это уже не просто картинка, а почти маленький браузерный пайплайн.
Так появился full_svg_flutter — моя попытка приблизить поведение SVG во Flutter к тому, как мы привыкли видеть его в браузере.
Веб-команда отдаёт SVG.
В Chrome он работает.
В Safari — тоже.
Во Flutter — внезапно превращается в статичную картинку, частично ломается или требует обходных путей вроде GIF, видео, Lottie или ручной переписи анимации.
Я попробовал решить именно этот класс проблем и в итоге написал пакет full_svg_flutter — SVG-рендерер для Flutter, который старается приблизить поведение SVG к тому, как его ожидаешь увидеть в браузере.
В статье расскажу:
почему animated SVG — это не “просто нарисовать path”;
какие подсистемы приходится реализовывать;
где такой пакет реально полезен;
где он, наоборот, не нужен;
и почему в какой-то момент начинаешь понимать, что внутри делаешь маленький браузер.
Пакет [1]
Если говорить про обычные статичные SVG, экосистема Flutter давно уже закрывает много задач достаточно хорошо.
Но как только речь заходит о реальных SVG из production, быстро выясняется, что «поддержка SVG» и «поддержка SVG примерно как в браузере» — это совсем разные уровни.
Типичный сценарий выглядит так:
дизайнер или веб-команда отдаёт SVG;
этот SVG уже работает на web;
внутри него могут быть:
SMIL-анимации,
CSS keyframes,
path morphing,
masks,
clipping,
filters,
text / textPath,
nested transforms,
defs, use, ссылки по id,
стили, заданные и атрибутами, и через CSS;
потом этот же файл хотят использовать во Flutter.
И вот здесь начинается самое интересное.
Потому что файл, который кажется “обычным SVG”, на деле может содержать довольно сложное поведение, которое в браузере давно воспринимается как само собой разумеющееся.
Снаружи задача для разработчика должна выглядеть максимально просто:
FSvgPicture.asset('assets/animated.svg')
Идея в том, что человеку не должно быть нужно думать о том, как:
распарсить XML;
построить DOM;
разрешить стили;
вычислить transform tree;
отследить timeline анимаций;
интерполировать path data;
применить filters;
понять, что можно кешировать, а что надо пересчитывать каждый frame.
В идеале — передал SVG и получил поведение, максимально близкое к ожидаемому.
Я специально хочу проговорить это в начале, потому что иначе статья легко скатывается в маркетинг.
full_svg_flutter — это не универсальная серебряная пуля.
Если коротко:
flutter_svg — отлично подходит для большого количества статичных SVG-сценариев;
Lottie — отлично подходит, если source of truth у команды именно Lottie;
Rive — отлично подходит, если анимация сделана в Rive;
PNG/WebP — иногда вообще лучший ответ, если задача чисто декоративная и интерактивность не нужна.
Мой use case другой:
SVG уже существует как исходный артефакт. Он работает в браузере. И хочется, чтобы именно этот SVG работал во Flutter без конвертации в GIF, видео или другой формат.
На первый взгляд кажется: ну что там, SVG - это же просто вектор.

Но как только начинаешь смотреть на реальные файлы, становится ясно, что SVG — это не просто «нарисовать набор path‑ов».
Это сразу несколько слоёв:
документная структура;
стили и наследование;
ссылки между элементами;
система координат;
таймлайн анимаций;
трансформации;
фильтры;
текст;
порядок отрисовки;
частичное обновление дерева;
кеширование.
То есть по сути ты имеешь дело не просто с картинкой, а с чем-то между:
DOM,
style engine,
animation engine,
render tree.
И как только начинаются реальные 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.
То есть “поддержать одну фичу” часто означает, что надо уже иметь пол-нормальной внутренней модели документа.
Первое, без чего всё быстро разваливается, — это внутренний DOM.
Нужно не просто распарсить XML в набор объектов, а иметь структуру, где каждый элемент знает:
свои атрибуты;
детей;
родителя;
id, class;
computed style;
transform;
paint properties;
visibility / display;
связи с defs, use, clipPath, mask, gradients и filters.
Без этого сложно корректно поддержать даже базовые зависимости между элементами, не говоря уже об анимации.
Одна из самых коварных частей SVG — стили могут приходить отовсюду.
Например, один и тот же fill может быть задан так:
<path fill="red" />
или так:
<path style="fill:red;" />
или так:
<style>
.logo-shape {
fill: red;
}
</style>
<path class="logo-shape" />
И всё это ещё может наследоваться.
Если не построить слой computed styles, очень быстро начинаются странные рассинхроны: в одном SVG что-то красится не тем цветом, в другом ломается stroke, в третьем не применяются классы.
Как только появляется SMIL или CSS-анимация, нужна система времени.
То есть недостаточно “на старте прочитать параметры анимации”.
Нужно уметь:
определять текущий момент времени;
вычислять активность анимации;
поддерживать begin, dur, repeatCount, repeatDur, fill;
интерполировать значения;
учитывать easing / splines;
применять результат к целевому атрибуту;
корректно инвалидировать render state.
И всё это должно быть синхронизировано с frame lifecycle Flutter.
Path morphing — один из самых интересных и болезненных случаев.
Если очень упростить, то мало просто взять две строки с d="" и попробовать “смешать” числа.
Чтобы morphing был корректным, нужно:
распарсить path data;
нормализовать команды;
привести относительные и абсолютные команды к совместимому виду;
убедиться, что структуры path-ов сопоставимы;
интерполировать параметры команд;
корректно собирать результат обратно.
На демо-файлах это можно “почти сделать” быстро.
На реальных SVG — начинается веселье.
SVG-фильтры — это отдельный мир.
Даже если брать относительно базовые вещи вроде blur, color matrix, blend/composite или drop shadow, быстро выясняется, что вопрос не только в поддержке операций, но и в том:
как считать bounds;
как не рисовать слишком много;
как кешировать промежуточный результат;
как пересчитывать только то, что действительно изменилось;
как не убить производительность на анимациях.
Фильтры — одно из первых мест, где архитектурные компромиссы начинают ощущаться очень явно.
Со статикой всё относительно приятно: можно много кешировать.
С анимированными 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.
Потому что пользователь замечает не “среднюю температуру по больнице”, а лаги и микрофризы.

Если очень грубо, внутренний пайплайн выглядит так:
SVG XML
Parser
SVG DOM
Style resolver / computed styles
Animation engine / timeline
Render tree
Painter / canvas layer
Cache layer
Flutter widget API
Это, конечно, упрощение, но именно такая ментальная модель помогает объяснить, почему задача внезапно оказывается намного шире, чем просто “отрисовать вектор”.
Снаружи хотелось сделать API максимально узнаваемым для Flutter-разработчика.
Например:
FSvgPicture.asset('assets/animated.svg')
Идея в том, чтобы migration path был максимально простым и не требовал “учить новый мир” просто ради того, чтобы получить анимированный SVG.
Я вижу несколько основных сценариев.
Это главный случай.
То есть команда не хочет экспортировать SVG в другой формат, а хочет использовать один и тот же артефакт между web и mobile.
Когда уже есть готовые SVG, которые ведут себя в браузере как нужно, и хочется приблизить это поведение во Flutter.
Например:
промо-экраны;
dashboard-like интерфейсы;
игровые / iGaming-подобные интерфейсы;
интерактивные иллюстрации;
сложные векторные элементы.
Masks, filters, clipping, text, nested transforms, animation combinations — всё это как раз тот класс задач, ради которого вообще имеет смысл заморачиваться отдельным renderer.
Важно честно проговорить и это.
Я бы не тянул full_svg_flutter, если:
у вас обычные статичные иконки и всё уже работает;
анимация изначально создаётся под Lottie;
анимация делается в Rive;
графику можно без потерь заменить на PNG/WebP;
UI-эффект проще, надёжнее и дешевле написать нативно во Flutter;
важнее максимальная простота и минимальные накладные расходы, чем совместимость SVG.
Иначе можно начать использовать слишком тяжёлый инструмент там, где задача реально решается проще.
Наверное, самое интересное ощущение было таким:
чем дальше продвигаешься, тем меньше это похоже на “библиотеку для 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]
Сейчас мне особенно интересны две вещи.
Я хочу прогонять пакет не только на красивых demo-assets, но и на реальных SVG, которые:
работают в Chrome/Safari;
становятся статичными во Flutter;
используют SMIL или CSS animation;
используют mask, clipPath, filter;
содержат textPath;
ломаются на стыке нескольких фич.
Если у вас есть такие примеры — буду очень рад, если вы скинете их в issues или в комментарии или на почту denis.nadey@gmail.com или telegram @denisdandy
Я хочу отдельно собрать воспроизводимый 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 — присылайте.
Такие кейсы для меня сейчас ценнее любой синтетики.
Ссылки:
Package: https://pub.dev/packages/full_svg_flutter [1]
GitHub: https://github.com/denisnadey/flutter_full_svg_support [2]
Возможные темы следующей статьи:
как устроен SVG animation timeline;
path morphing: почему нельзя просто интерполировать строку d;
как работает SVG CSS cascade;
как мерить frame stability во Flutter;
как тестировать SVG renderer на production-файлах.
Автор: denisdandy
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/dart/450853
Ссылки в тексте:
[1] Пакет: https://pub.dev/packages/full_svg_flutter
[2] https://github.com/denisnadey/flutter_full_svg_support: https://github.com/denisnadey/flutter_full_svg_support
[3] Источник: https://habr.com/ru/articles/1030722/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1030722
Нажмите здесь для печати.