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

Страницы:  1

Ответить
 

Professor Seleznov


Для контекста: я Flutter-инженер и техлид, последние годы работаю с production-приложениями на Flutter — мобильными, web и гибридными. В моей практике были fintech, маркетплейсы, food delivery и iGaming-продукты, где к UI обычно предъявляют довольно жёсткие требования: сложная графика, анимации, дизайн-системы, производительность, стабильность и предсказуемая доставка фич.
pic
full_flutter_svg (пришлось рисовать через ai из меня плохой художник)
Веб-команда отдаёт 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 к тому, как мы привыкли видеть его в браузере.
Почему animated SVG не работает во Flutter «как в браузере» — и как я попытался это исправить
Веб-команда отдаёт SVG.
В Chrome он работает.
В Safari — тоже.
Во Flutter — внезапно превращается в статичную картинку, частично ломается или требует обходных путей вроде GIF, видео, Lottie или ручной переписи анимации.
Я попробовал решить именно этот класс проблем и в итоге написал пакет full_svg_flutter — SVG-рендерер для Flutter, который старается приблизить поведение SVG к тому, как его ожидаешь увидеть в браузере.
В статье расскажу:
  • почему animated SVG — это не “просто нарисовать path”;
  • какие подсистемы приходится реализовывать;
  • где такой пакет реально полезен;
  • где он, наоборот, не нужен;
  • и почему в какой-то момент начинаешь понимать, что внутри делаешь маленький браузер.
Пакет
Откуда вообще берётся проблема
pic
иногда мы даже не видим ничего (даже первого кадра)
Если говорить про обычные статичные 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, видео или другой формат.
Почему animated SVG — это сильно сложнее, чем кажется
На первый взгляд кажется: ну что там, SVG - это же просто вектор.
pic
Но как только начинаешь смотреть на реальные файлы, становится ясно, что SVG — это не просто «нарисовать набор path‑ов».
Это сразу несколько слоёв:
  • документная структура;
  • стили и наследование;
  • ссылки между элементами;
  • система координат;
  • таймлайн анимаций;
  • трансформации;
  • фильтры;
  • текст;
  • порядок отрисовки;
  • частичное обновление дерева;
  • кеширование.
То есть по сути ты имеешь дело не просто с картинкой, а с чем-то между:
  • DOM,
  • style engine,
  • animation engine,
  • render tree.
И как только начинаются реальные SVG-анимации, это становится очень заметно.
Какие фичи реальных SVG быстрее всего ломают «простое решение»
Вот список того, что чаще всего делает задачу нетривиальной:



  • 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
  • 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 может быть задан так:

или так:

или так:



И всё это ещё может наследоваться.
Если не построить слой 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.
Потому что пользователь замечает не “среднюю температуру по больнице”, а лаги и микрофризы.
Архитектура в двух словах
pic
Если очень грубо, внутренний пайплайн выглядит так:
  • 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.
Где такой пакет реально полезен
Я вижу несколько основных сценариев.
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.
Где пакет, наоборот, не нужен
Важно честно проговорить и это.
Я бы не тянул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. Реальные 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 — присылайте.
Такие кейсы для меня сейчас ценнее любой синтетики.
Ссылки: Возможные темы следующей статьи:
  • как устроен SVG animation timeline;
  • path morphing: почему нельзя просто интерполировать строку d;
  • как работает SVG CSS cascade;
  • как мерить frame stability во Flutter;
  • как тестировать SVG renderer на production-файлах.
-Источник
 
Loading...
Error