Timing charts як інструмент для планування SMIL-анiмацiй в SVG
Матеріал розбирає підхід Йогана Гроблера до планування SMIL-анімацій в SVG за допомогою timing charts і синхронізації через syncbase-значення.
SMIL проти CSS/JS: у чому складність
SMIL вбудовується прямо в SVG і, на відміну від JavaScript, працює навіть коли файл підключають через <img>. Це дає змогу:
- анімути будь-які SVG-атрибути, включно з тими, для яких ще немає CSS-еквівалентів;
- обійтися без скриптів там, де вони небажані або заборонені;
- використовувати SVG як звичайну картинку, але з живою анімацією.
Втім, у SMIL є суттєве обмеження: один тег анімації керує лише однією властивістю одного елемента. Для зміни кольору й прозорості одного об’єкта вже потрібні два теги:
<animate
attributeName="fill"
to="someOtherColor"
dur="someDuration"
/>
<animate
attributeName="opacity"
to="someOtherValue"
dur="someDuration"
/>
Якщо таких елементів десятки, розмітка швидко розростається. Тому автор пропонує не сперечатися з цим, а краще навчитися управляти складністю — через планування часу й синхронізацію.
Timing chart: як «намалювати» час анімації
Авторка підходу (команда автора статті) користується timing chart — схемою, де кожна «доріжка» відповідає окремій анімації, а відрізок на ній показує час від початку до кінця цієї анімації.
Ідея проста:
- одна лінія — одна анімація (один SMIL-тег);
- початок відрізка — момент
begin, кінець — моментend; - лінії можуть іти паралельно (одночасні ефекти), накладатися (перекриття) чи йти каскадом одна за одною.
Така схема не зобов’язана бути в масштабі до мілісекунди — головне, щоб зберігалися відносні проміжки. Часто достатньо підписати тривалість біля сегмента, не змушуючи себе креслити з лінійкою.
Практична користь:
- ви чітко бачите послідовність дій: що запускає що;
- легше експериментувати з варіантами: зсунули сегмент — отримали нову поведінку;
- можна зафіксувати кілька версій й порівнювати їх «на папері», а не лише в браузері.
Syncbase: як SMIL вміє «прив’язуватися» до інших анімацій
Одна з сильних сторін SMIL — синхронізація анімацій між собою. Для цього використовуються так звані syncbase-значення в атрибуті begin:
іншаАнімація.begin— почати разом із початком іншої;іншаАнімація.end— почати, коли інша завершиться;- додатково можна додати зміщення:
+ 300msабо- 300ms.
Наприклад, колір стартує в абсолютний час, а прозорість — відносно моменту завершення кольору:
<!-- Starts at an absolute time -->
<animate
id="colorChange"
begin="1s"
...
/>
<!-- Starts relative to when #first ends -->
<animate
id="opacityChange"
begin="colorChange.end - 300ms"
...
/>
Тут:
#colorChangeмає конкретнийbegin="1s";#opacityChangeстартує за300мілісекунд до завершенняcolorChange.
Від’ємні офсети мають нюанс: вони можуть «залазити» в час до завантаження документа або до події (наприклад, кліку). У такому випадку браузер просто перестрибує до того стану, в якому була б анімація, якби вона стартувала раніше. Повного програвання «від самого початку» не відбудеться, якщо частина таймлайну залишилася «в минулому».
Автор радить вибирати «головну» анімацію (primary) і задавати всім іншим begin="primary.begin". Якщо потім потрібно посунути всю групу в часі, ви змінюєте лише одне місце.
Кейс: триточковий лоадер на opacity
Крок 1. Вибір способу виводу SVG і налаштування reduced motion
Авторка описує кілька варіантів того, як поважати prefers-reduced-motion користувача для SMIL-анімацій:
<picture>+<source>зmedia— показувати різні файли (анімаційний / статичний) залежно від налаштувань;- один SVG із inline CSS та
@media (prefers-reduced-motion), що вмикає/вимикає різні версії черезdisplay: none(але тут описано потенційні проблеми в деяких середовищах і браузерах, посиланням на баг-репорти); - CSS background-image, де в медіа-запиті для reduced motion вказується статичне зображення;
- SVG <view> — альтернативні представлення в одному файлі;
- JavaScript +
matchMedia()і SMIL DOM — управляти тим, які анімації стартують.
У прикладі з трьома крапками спершу обирається відносно спокійний варіант — анімується лише opacity, і SVG підключають через <img>. Для варіантів із рухом автор радить переходити до <picture> з альтернативними джерелами.
Крок 2. Малюємо базову графіку
Створюється класичний триточковий лоадер — три <circle> в SVG. Автор рекомендує:
- тим, хто не любить писати SVG вручну, — графічні редактори на кшталт Inkscape;
- уважно працювати з ID елементів: у деяких редакторах (зокрема Inkscape) назва шару — це не завжди справжній
idелемента; його краще задавати через властивості об’єкта або XML-редактор; - зберігати як «optimized SVG», аби вичистити зайві метадані.
Крок 3. Плануємо анімації й систему ID
Анімація для кожної точки розбивається на два окремі SMIL-теги:
- fade-in для точки;
- fade-out для тієї ж точки.
Загалом виходить 6 тегів <animate>. Схема ідентифікаторів:
- імена анімацій:
fadeIn,fadeOut; - суфікси:
Left,Middle,Right.
Приклад fade-in для лівої точки:
<animate
id="fadeInLeft"
href="#leftDot"
attributeName="opacity"
from="0"
to="1"
...
/>
Fade-out для середньої:
<animate
id="fadeOutMiddle"
href="#middleDot"
attributeName="opacity"
from="1"
to="0"
...
/>
Продумана схема ID сильно полегшує побудову timing chart: кожен відрізок на схемі легко співвіднести з конкретним SMIL-тегом.
Крок 4. Виставляємо час і варіанти патернів
Щоб зменшити число змінних, у кейсі всі шість анімацій мають однакову dur, а syncbase-значення спочатку задаються без офсетів.
Одна з базових конфігурацій:
- кожна точка по черзі з’являється (fade-in) зліва направо;
- усі три одночасно зникають (груповий fade-out);
- завершення fade-out знову запускає цикл.
Старт серії fade-in організовано так:
Тут:
fadeInLeftстартує відразу (або після завершенняfadeOutLeft— для циклу);fadeInMiddleчекає завершення лівої точки;fadeInRight— завершення fade-in середньої.
Для групового fade-out автор обирає #fadeOutLeft як «головну» анімацію для синхронізації:
Якщо потім вам потрібно, наприклад, «зрушити» момент зникнення всіх точок — ви просто змінюєте begin у #fadeOutLeft.
Альтернативні ритми без зміни графіки
Далі автор показує, як змінюється відчуття анімації, якщо:
- замість групового fade-out зробити ступінчастий (точки зникають по черзі через syncbase, як у прикладі з fade-in);
- запускати fade-out лівої точки не від завершення правої, а раніше — від завершення середньої чи навіть лівої fade-in;
- починати взагалі з fade-out, а не з fade-in, або стартувати з центральної точки.
Всі ці варіації зручно шукати саме через timing chart: ви буквально пересуваєте відрізки по осі часу й дивитеся на зміну патерну, замість постійно переписувати час у коді «наосліп».
Ускладнюємо спінер: clipPath та кілька типів анімацій
Коли анімацій стає більше, у коді легко загубитися. Автор розширює приклад зі спінером, щоб показати, як timing charts допомагають тримати ситуацію під контролем.
Додаємо clipPath і рух прямокутників
До кожної точки додається <rect>, який переносять у <clipPath>. Самі точки малюються з обводкою (stroke), а видима частина визначається прямокутниками-кліперами.
Структура в <defs> виглядає приблизно так:
Потім кожній точці додається clip-path:
Через те, що додано обводку, потрібно компенсувати її вплив на радіус — зменшити r на половину stroke-width, щоб візуальний розмір не змінився:
Нове джерело таймінгу: рух кліперів
У складнішій версії з’являється нова «провідна» анімація #moveClipPathLeft, яка рухає прямокутник-кліпер лівої точки і задає старт для всього циклу. Між завершенням fade-out та наступним запуском додається пауза в 1 секунду:
Автор звертає увагу, що теоретично можна було б анімувати transform (через <animateTransform>), але тоді потрібно враховувати, які саме типи перетворень підтримуються для конкретного елемента і як правильно «повертати» його у вихідну позицію для безшовного циклу. Зміна координати y у прямокутника простіша й більш передбачувана.
Для середньої точки прямокутник рухається в інший бік, що створює трохи інший візуальний ритм:
Поєднуємо fill-opacity і opacity
Ще одна ідея кейсу — розвести заливку й обводку по різних властивостях:
- fade-in працює з
fill-opacity(плавно з’являється лише заливка точки, stroke може бути видимим раніше); - fade-out працює з
opacityі впливає одразу на все (і fill, і stroke).
Синхронізація fade-in прив’язується до кінця руху відповідного прямокутника-кліпера:
Для fade-out автор повертається до групового гасіння всіх трьох точок. Тут уже не використовується fill="freeze" — це дає можливість обійтися без зайвих <set> для скидання значень, бо властивість сама повернеться до початкового стану після завершення анімації:
Коли потрібні <set> для скидання стану
Якщо ж для деяких <animate> ви все ж використовуєте fill="freeze", тоді в кінці циклу потрібно вручну повертати властивості «на нуль». У прикладі це робиться через <set>:
- скидання
fill-opacityдля лівої точки після завершення fade-out:
- скидання
opacityдля середньої точки (якщо її також «заморожували»):
- повернення правого кліпера у вихідну позицію по осі
y:
На timing chart такі <set> зручно позначати точками без відрізка — вони не мають тривалості, а просто миттєво змінюють стан у певний момент.
Навіщо все це: роль timing charts у складних анімаціях
Автор підкреслює, що як тільки анімація виходить за межі «двох простих кроків», ви фактично займаєтеся оркестрацією. Це нагадує складний механізм, де без попереднього плану легко щось зламати, пересмикнути, створити дивні накладки.
Timing chart у цій картині виконує кілька завдань:
- заставляє вас спочатку описати логіку і послідовність, а вже потім писати SMIL;
- слугує «мапою» для підтримки й змін: коли через півроку потрібно трохи змінити ритм або додати ефект, ви не шукаєте хаотично по коду, а дивитеся на схему;
- полегшує створення варіантів: те, що в коді виглядало б як серія правок у різних
beginіdur, на схемі — просто нове розташування відрізків.
При цьому timing charts не зменшують реальну кількість SMIL-тегів — верстка все одно залишається докладною й багатошаровою. Але вони дають огляд того, що і коли має трапитися, а syncbase-значення в SMIL дозволяють формалізувати цю логіку напряму в розмітці.
Сам підхід із timing charts не обмежується SMIL: його можна так само застосовувати до CSS- або JS-анімацій. Просто в SMIL, завдяки вбудованим механізмам синхронізації, цей підхід особливо органічний.
Джерело: Johan Grobler — Smashing Magazine