Руководство по SVG-анимациям (SMIL)

Перевод статьи A Guide to SVG Animations (SMIL) с сайта css-tricks.com, автор — Сара Суэйдан. Публикуется с разрешения автора.

Перед вами гостевой постинг Сары Суэйдан. Сара — мастер докапываться до самых глубин классных веб-новинок, подробно и понятно разбирая их до основания. Здесь она погружается в недра SMIL (и смежных технологий) и синтаксиса анимаций, встроенного в сам SVG, и делится с нами этим внушительным руководством.

Введение

SVG-графику можно анимировать с помощью анимационных элементов. Эти анимационные элементы изначально были определены в спецификации анимаций SMIL. В их число входят:

  • <animate> — позволяющий анимировать скалярные атрибуты и свойства в течение периода времени;
  • <set> — являющийся удобным сокращением для animate, что удобно для задания анимаций для нечисловых атрибутов и свойств, наподобие свойства visibility;
  • <animateMotion> — позволяющий двигать элемент по заданной траектории;
  • <animateColor> — изменяющий значение цвета каких-либо атрибутов или свойств с течением времени. Заметьте, что элемент <animateColor> устарел, и вместо него рекомендуется использовать обычный элемент animate для свойств, принимающих значения цвета. Тем не менее, он всё еще есть в спецификации SVG 1.1, где он явно помечен как устаревший; из спецификации SVG 2 он удален полностью.

В дополнение к анимационным элементам, определенным в спецификации SMIL, SVG включает расширения, совместимые со спецификацией SMIL animations; эти расширения включают атрибуты, расширяющие функциональность элемента <animateMotion>, и дополнительные анимационные элементы. В расширения SVG входят:

  • <animateTransform> — позволяет анимировать один из атрибутов трансформации SVG во времени, например, атрибут transform;
  • path (атрибут) — позволяет использовать все возможности синтаксиса данных для SVG-контуров в атрибуте path элемента animateMotion (SMIL Animation разрешает лишь подмножество соотв. синтаксиса в атрибуте path). Мы еще поговорим о animateMotion в следующем разделе.
  • <mpath> — используется в сочетании с элементом animateMotion для указания траектории движения, которую можно использовать — логично — для движения по траектории. Элемент mpath ставится внутрь элемента animateMotion, перед закрывающим тегом.
  • keypoints (атрибут) — используется как атрибут для animateMotion, чтобы с точностью управлять скоростью движения по траектории.
  • rotate (атрибут) — используется как атрибут для animateMotion для управления тем, будет ли объект автоматически поворачиваться так, чтобы его ось X указывала в ту же (или противоположную) сторону, что и касательный вектор направления траектории движения. Этот атрибут — ключ к тому, чтобы анимация движения по траектории работала, как ожидается. Подробнее об этом в разделе о animateMotion.

По своей природе SVG-анимации напоминают CSS-анимации и переходы. Создаются ключевые кадры, объекты движутся, цвета меняются и т.п. Однако они могут делать и то, чего CSS-анимации не могут, что мы еще рассмотрим.

Зачем использовать SVG-анимации?

SVG-картинки могут быть стилизованы и анимированы с помощью CSS (см. слайды). По сути, все трансформации и анимированные переходы, которые можно применять к HTML-элементам, применимы и к SVG-элементам. Но есть SVG-свойства, которые не анимируются силами CSS, а только силами SVG. Например, SVG-путь изначально содержит набор данных (атрибут d=""), которые определяют его форму. Эти данные можно изменять и анимировать с помощью SMIL, но не с помощью CSS. Это связано с тем, что SVG-элементы описываются набором атрибутов, известных как атрибуты представления. Некоторые из этих атрибутов можно устанавливать, менять и анимировать с помощью CSS, а некоторые нельзя.

Итак, многие анимации и эффекты на данный момент силами CSS просто недостижимы. Недостающие возможности CSS-анимаций для SVG можно восполнить либо использованием JavaScript, либо декларативными SVG-анимациями, основанными на SMIL.

Если вы предпочитаете JavaScript, я рекомендую библиотеку snap.svg Дмитрия Барановского, которую описывают как «SVGшный jQuery». Вот собрание примеров для нее.

Если же вам больше по душе декларативный подход к анимациям, вы можете воспользоваться анимационными SVG-элементами, которые мы рассматриваем в этом руководстве!

Еще одно преимущество SMIL перед JS-анимациями — то, что JS-анимации не работают, если SVG встроен в страницу в виде img или использован как background-image в CSS. SMIL-анимации работают в обоих случаях (по крайней мере, должны, поддержка браузерами на подходе). Это большое преимущество, по-моему. Оно может оказаться решающим, чтобы вы предпочли SMIL прочим вариантам. И эта статья — руководство, чтобы помочь вам начать использовать SMIL уже сегодня.

Браузерная поддержка и варианты для подстраховки

Поддержка SMIL браузерами вполне прилична. Это работает во всех браузерах, кроме IE и Оперы Мини. За подробностями о поддержке можете обратиться к таблице совместимости на Can I Use.

Если вам нужен резервный вариант на случай неподдержки SMIL-анимаций, вы можете «на лету» проверить наличие поддержки в браузере с помощью Modernizr. Если SMIL не поддерживается, вы можете предусмотреть какую-то замену (JS-анимацию, альтернативный интерфейс и т.д.).

Указание целевого объекта анимации с помощью xlink:href

Какой из четырех анимационных элементов вы бы ни выбрали, вам нужно указать целевой объект, для которого этот элемент задает анимацию.

Чтобы указать этот объект, можно использовать атрибут xlink:href. Этот атрибут принимает URI-ссылку на элемент, который будет объектом нашей анимации и который, соответственно, и будет изменяться с течением времени. Целевой объект должен быть частью текущего фрагмента SVG-документа.

<rect id="cool_shape" ... />
<animation xlink:href="#cool_shape" ... />

Если вы уже сталкивались с анимационными SVG-элементами, вы, вероятно, видели их вложенными прямо в тот элемент, который ими предполагалось анимировать. Это тоже возможно, согласно спецификации:

Если атрибут xlink:href не задан, то целевым элементом становится непосредственный родительский элемент текущего анимационного элемента.

<rect id="cool_shape" ... >
  <animation ... />
</rect>

Так что если вы хотите «инкапсулировать» анимацию в элементе, к которому она применяется, вы можете делать так. А если вы хотите вынести анимации в какое-то другое место документа, вы можете сделать и это, указав целевой объект каждой анимации с помощью xlink:href — оба способа работают отлично.

Указание целевого свойства анимации с помощью attributeName и attributeType

У всех анимационных элементов может быть и еще один общий атрибут: attributeName. Атрибут attributeName используется для указания имени атрибута, который вы анимируете.

Например, если вы хотите анимировать положение центра <circle> на оси X, вы можете задать cx в качестве значения атрибута attributeName.

attributeName принимает лишь одно значение, а не список значений, так что вы можете анимировать только по одному атрибуту за раз. Если вы хотите анимировать более одного атрибута, вам нужно определить более одной анимации для одного элемента. Мне бы хотелось, чтобы это было иначе, и здесь я вижу преимущество CSS-анимаций перед SMIL. Но опять же, из-за значений, возможных для других анимационных атрибутов (следующими в очереди на рассмотрение), есть смысл определять только одно имя атрибута за раз, иначе значения других атрибутов могут стать слишком сложными для работы с ними.

Указывая имя атрибута, вы можете добавить XMLNS-префикс (сокращение от «пространство имен XML»), чтобы обозначить пространство имен для атрибута. Пространство имен также можно указать с помощью атрибута attributeType. Например, некоторые атрибуты входят в пространство имен CSS (что значит, что они могут быть также CSS-свойствами), а другие есть только в XML. Таблицу этих атрибутов можно посмотреть здесь. В этой таблице приведены не все атрибуты SVG. Это только те из них, что можно задавать с помощью CSS. Некоторые из них уже доступны как CSS-свойства.

Если значение attributeType не заданано явно или равно auto, браузер должен сначала поискать соответствующее имя свойства в списке CSS-свойств, и, если ничего не найдено, поискать в пространстве имен XML по умолчанию для данного элемента.

Например, следующий код анимирует opacity SVG-прямоугольника. Так как атрибут opacity доступен также как CSS-свойство, значение attributeType указывает на пространство имен CSS:

<rect>
  <animate attributeType="CSS" attributeName="opacity" 
           from="1" to="0" dur="5s" repeatCount="indefinite" />
</rect>

Мы пройдемся по другим анимационным атрибутам в нижеследующих примерах. Если не указано иное, все анимационные атрибуты — общие для всех анимационных элементов.

Анимация атрибута элемента от одного значения до другого в течение отрезка времени и указание конечного состояния: from, by, to, dur и fill

Давайте начнем с перемещения круга из одного положения в другое. Будем делать это, меняя значение его атрибута cx (который указывает X-координату его центра).

Будем использовать для этого элемент <animate>. Этот элемент используется для анимации одного атрибута за раз. Атрибуты, значениями которых являются числа и цвета, обычно анимируются с помощью <animate>. За списком атрибутов, которые можно анимировать, обратитесь к этой таблице.

Чтобы изменить значение с одного на другое за период времени, используются атрибуты from, to, и dur. В дополнение к ним вы можете указать, когда анимация должна начаться, с помощью атрибута begin.

<circle id="my-circle" r="30" cx="50" cy="50" fill="orange" />

  <animate 
    xlink:href="#my-circle"
    attributeName="cx"
    from="50"
    to="450" 
    dur="1s"
    begin="click"
    fill="freeze" />

В примере выше, мы определили круг и затем вызвали анимацию для этого круга. Центр круга перемещается с начальной позиции в 50 единиц до 450 единиц вдоль оси X.

Атрибуту begin задано значение click. Это значит, что круг начинает двигаться, когда его кликнули. Можно также задать ему значение времени. Например, begin="0s" запускает анимацию сразу же, как только страница загрузилась. Вы можете отсрочить анимацию, задавая положительные значения времени. Например, begin="2s" запустит анимацию через две секунды после загрузки.

Еще более интересная особенность begin — то, что вы можете задавать значения типа click + 1s, чтобы запустить анимацию через секунду после клика! Более того, вы можете использовать другие значения, позволяющие синхронизировать анимации без необходимости рассчитывать длительности и задержки других анимаций. Подробнее об этом позже.

Атрибут dur похож на эквивалент animation-duration в CSS.

Атрибуты from и to похожи на ключевые кадры from и to в блоке @keyframe CSS-анимации:

@keyframes moveCircle {
  from { /* начальное значение */ }
  to { /* конечное значениe */ }
}

Атрибут fill (который довольно неудачно получил одинаковое название с атрибутом fill, определяющим цвет заливки элемента) похож на свойство animation-fill-mode, определяющее, должен ли элемент вернуться в начальное состояние после завершения анимации, или нет. Значения похожи на аналогичные в CSS, за исключением других имен:

  • freeze: результат анимации определен как «застывший» в конечном значении активного периода анимации. Результат анимации остается «застывшим», пока документ открыт (или до перезапуска анимации).
  • remove: результат анимации убирается (больше не применяется), когда активный период анимации завершился. Как только достигнут конец анимации, анимация больше не влияет на целевой объект (если ее не перезапустить).

Попробуйте менять значения в живом демо-примере, чтобы посмотреть, как они влияют на анимацию:

Просмотреть пример вживую на CodePen.

Атрибут by служит для указания относительного сдвига анимации. Как можно предположить из названия, с его помощью вы можете указать величину, на которую анимация должна продвинуться. Эффект от by виден практически только тогда, когда вы продвигаетесь по анимации дискретными шагами, наподобие того, как это работает с CSS-функцией steps(). SVG-эквивалент самой CSS-функции steps()— это calcMode="discrete". Мы дойдем до атрибута calcMode далее в статье.

Другой случай, когда эффект от by более очевиден — когда вы задаете только атрибут to. Например, если вы будете использовать его с элементом set, который мы рассмотрим далее в статье.

И последнее, но немаловажное: by также оказывается полезным, когда вы работаете с относительными и накопительными анимациями. До них мы тоже дойдем чуть позже.

Перезапуск анимаций с помощью restart

Бывает полезно не дать анимации перезапуститься, пока она активна. Для этого SVG предлагает атрибут restart. Вы можете задать ему одно из трех возможных значений:

  • always: анимация может быть перезапущена в любой момент. Значение по умолчанию.
  • whenNotActive: анимация может быть перезапущена, только когда неактивна (т.е. после завершения активности). Попытки перезапустить анимацию во время активного промежутка игнорируются.
  • never: элемент не может быть перезапущен весь остаток простой длительности его родительского контейнера времени (в случае SVG, поскольку родительским контейнером времени является фрагмент SVG-документа, анимация не может быть перезапущена, пока открыт документ).

Именование и синхронизация анимаций

Допустим, мы хотим анимировать положение и цвет круга, так, чтобы изменение цвета произошло после завершения движения. Мы можем сделать это, задав атрибуту begin анимации изменения цвета значение, равное duration анимации движения; так мы делали бы это и в CSS.

Однако у SMIL есть замечательная возможность обработки событий. Мы уже отметили, что атрибут begin принимает значения типа click + 5s. Это значение называется «значением события», и в данном случае состоит из ссылки на событие, за которым следует «часовое значение». Интересный момент заключен в названии второй части: «часовое значение». Почему не просто «значение времени»? Разгадка в том, что вы можете буквально задавать значение как на часах, напр. «10min» или «01:33», что эквивалентно «1 мин 33 с», или даже «02:30:03» (2 ч 30 мин 3 с). На момент написания статьи, часовые значения не реализованы полностью ни в одном браузере.

Так что, если мы вернемся к предыдущему демо и используем click + 01:30, если браузер начнет его поддерживать, анимация запустится через 1 мин 30 с после клика на круг.

Другой тип значений, который он может принимать — ID другой анимации, за которым следует ссылка на событие. Если у вас есть две (и более) анимации (неважно, применяются ли они к одному и тому же элементу или нет!) и вы хотите синхронизировать их так, чтобы одна из них запускалась в зависимости от другой, вы можете сделать это, даже не зная длительности второй анимации.

Например, в следующем демо, синий прямоугольник начинает двигаться через 1 секунду после начала анимации круга. Это достигается заданием каждой анимации своего ID с последующим использованием этого ID с событием begin, как показано в следующем коде:

<circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

<rect id="blue-rectangle" width="50" height="50" x="25" y="200" fill="#0099cc"></rect>

  <animate 
    xlink:href="#orange-circle"
    attributeName="cx"
    from="50"
    to="450" 
    dur="5s"
    begin="click"
    fill="freeze" 
    d="circ-anim" />

  <animate 
    xlink:href="#blue-rectangle"
    attributeName="x" 
    from="50"
    to="425" 
    dur="5s"
    begin="circ-anim.begin + 1s"
    fill="freeze" 
    id="rect-anim" />

Именно begin="circ-anim.begin + 1s" — та часть, что указывает браузеру начать анимацию прямоугольника через 1 секунду после начала анимации круга. Вы можете проверить на живом примере:

Просмотреть пример вживую на CodePen

Можно также начать анимацию прямоугольника после завершения анимации круга, используя событие end:

<animate 
    xlink:href="#blue-rectangle"
    attributeName="x" 
    from="50"
    to="425" 
    dur="5s"
    begin="circ-anim.end"
    fill="freeze" 
    id="rect-anim"/>

Вы можете даже запустить ее в определенный момент до конца анимации круга:

<animate 
    xlink:href="#blue-rectangle"
    attributeName="x" 
    from="50"
    to="425" 
    dur="5s"
    begin="circ-anim.end - 3s"
    fill="freeze" 
    id="rect-anim"/>

Зацикливание анимаций с repeatCount

Если вы хотите проиграть анимацию больше одного раза, вам пригодится атрибут repeatCount. Можно указать желаемое количество повторений либо использовать ключевое слово indefinite, чтобы анимация повторялась бесконечно. Так что, чтобы повторить анимацию круга дважды, код должен выглядеть примерно так:

<animate 
    xlink:href="#orange-circle"
    attributeName="cx"
    from="50"
    to="450" 
    dur="5s"
    begin="click"
    repeatCount="2"
    fill="freeze" 
    id="circ-anim" />

Вы можете проверить на живом примере. Я установила 2 повторения для круга и indefinite для квадрата.

Просмотреть пример вживую на CodePen.

Заметьте, как анимация начинается заново с начального значения from, а не со значения, достигаемого в конце анимации. К сожалению, в SMIL нет способа для перехода туда и обратно между начальным и конечным значением, как позволяют CSS-анимации. В CSS свойство animation-direction указывает, должна ли анимация проигрываться в обратном направлении при некоторых (или всех) повторениях. Значение animation-direction: alternate означает, что нечетные циклы анимации проигрываются в прямом направлении, а четные — в обратном. Это значит, что первый цикл проигрывается от начала до конца, второй цикл — назад от конца к началу, третий — снова от начала до конца, и т.д.

Чтобы добиться этого в SMIL, вам придется воспользоваться JavaScript, чтобы явно менять значения атрибутов from и to. Джон МакПартланд из студии Big Bite Creative не так давно написал пост с объяснением, как он делал это для анимации иконки меню, над которой он работал.

Еще один выход — указать желаемое конечное значение как значение для середины, а фактическое конечное значение оставить тем же, что начальное. Например, вы можете задать анимацию, начинающуюся со значения from и заканчивающаяся тем же самым значением в to, разве что вам придется указать то значение, которое по смыслу должно быть конечным, в качестве промежуточного значения между from и to.

В CSS мы могли сделать это примерно так:

@keyframes example {
  from, to {
    left: 0;
  }

  50% {
    left: 300px;
  }
}

Эквивалент этого в SMIL — использование атрибута values, который мы вкратце разберем чуть позже.

Таким образом, вышеописанное решение может подойти или не подойти в зависимости от типа анимации, который вам нужен, и от того, нужна ли вам последовательность анимаций, их повторение или накопление их результата.

Вот симпатичная несложная бесконечная анимация с использованием задержек времени начала, созданная Майлзом Иламом:

See the Pen Hexagon Ripple by Miles Elam (@mileselam) on CodePen.

Ограничение времени повторения с помощью repeatDur

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

Время представления указывает точку на временной оси относительно начала отображения документа для данного фрагмента. Она указывается с помощью атрибута repeatDur. Его синтаксис похож на часовое значение, но отсчитывается не относительно события другой анимации или действия пользователя, а от начала отображения документа.

Например, следующий код остановит повторение анимации через 1 мин 30 с после появления документа:

<animate 
    xlink:href="#orange-circle"
    attributeName="cx"
    from="50"
    to="450" 
    dur="2s"
    begin="0s"
    repeatCount="indefinite"
    repeatDur="01:30" 
    fill="freeze" 
    id="circ-anim" />

И вот живой демо-пример:

 

Синхронизация анимаций на основе количества повторений

Теперь давайте вернемся на шаг назад, к вопросу синхронизации между двумя анимациями. Действительно, в SMIL вы можете синхронизировать анимации так, что одна анимация запускается в зависимости от количества повторений другой. Например, можно запустить анимацию после энного по счету повторения другой анимации плюс (или минус) произвольное количество времени.

Следующий пример запускает анимацию прямоугольника на втором повторении анимации круга:

<animate 
    xlink:href="#blue-rectangle"
    attributeName="x" 
    from="50"
    to="425" 
    dur="5s"
    begin="circ-anim.repeat(2)"
    fill="freeze" 
    id="rect-anim" />

А вот живой пример, в котором анимация прямоугольника запускается через 2 секунды после второго повторения анимации круга.

Просмотреть пример вживую на CodePen.

Прим. перев.: пример в оригинальной статье у меня не работает ни в одном браузере, поэтому я добавил свой видоизмененный вариант. Похоже, чтобы repeat(n) работал в Chrome, число повторений анимации должно быть не менее n+1, т.е. указанное повторение не должно быть последним. Вот этот вариант:

See the Pen Guide to SVG Animations by Ilya Streltsyn (@SelenIT) on CodePen.

И вот пример, который составил Дэвид Эйзенберг для книги «Основы SVG», 2-е издание.

Управление значениями ключевого кадра анимации: keyTimes и values

В CSS мы можем указывать, какие значения должно принимать анимируемое нами свойство в определенном кадре в течение анимации. Например, если вы анимируете левое смещение элемента, то вместо того, чтобы анимировать его, скажем, с 0 до 300 напрямую, вы можете анимировать его так, чтобы в некоторых кадрах оно принимало специальные значения, вроде таких:

@keyframes example {
  0% {
    left: 0;
  }
  50% {
    left: 320px;
  }
  80% {
    left: 270px;
  }
  100% {
    left: 300px;
  }
}

Эти 0%, 20%, 80% и 100% — кадры анимации, а значения внутри блока для каждого кадра — значения для данного кадра. Выше описан эффект, когда элемент «отскакивает от стенки», а затем возвращается в окончательное положение.

В SMIL можно управлять значениями для отдельных кадров похожим образом, но синтаксис другой.

Чтобы указать ключевые кадры, используйте атрибут keyTimes. А чтобы задать значения анимируемого свойства, используйте атрибуты values. В SMIL достаточно удобная система именования.

Если мы вернемся к нашему движущемуся кругу и используем значения наподобие тех, что в примере CSS-анимации выше, код будет выглядеть примерно так:

<animate 
    xlink:href="#orange-circle"
    attributeName="cx"
    from="50"
    to="450" 
    dur="2s"
    begin="click"
    values="50; 490; 350; 450"
    keyTimes="0; 0.5; 0.8; 1"
    fill="freeze" 
    id="circ-anim" />

Итак, что мы тут сделали?

Первое, что надо отметить — то, что значения времени ключевых кадров и промежуточных значений указываются в виде списков. Атрибут keyTimes представляет собой список значений времени, разделенных точкой с запятой, задающих темп анимации. Каждое время в списке соответствует значению в атрибуте values и определяет, когда это значение будет использовано в функции анимации. Каждое значение времени в списке keyTimes указывается как число с плавающей точкой от 0 до 1 (включительно), представляющее собой пропорциональное смещение относительно простой длительности анимационного элемента. Так что ключевые моменты времени очень похожи на аналогичные в CSS, только задаются не процентами, а дробью.

Вот живой пример для кода выше. Кликните по кругу, чтобы начать анимацию.

See the Pen Просмотреть пример вживую на CodePen.

Заметьте, что, если используется список значений, анимация будет применять значения последовательно в течение своего периода. Если задан values, любые значения атрибутов from, to и by игнорируются.

Здесь же стоит также упомянуть, что вы можете использовать атрибут values без атрибута keyTimes — значения автоматически распределяется через равномерные промежутки времени (для любого calcMode, кроме paced, подробнее об этом буквально в следующем разделе).

Управление темпом анимации с помощью произвольной функции плавности: calcMode и keySplines

Я снова обращусь к сравнению CSS и SMIL, потому что понять синтаксис и некоторые понятия SMIL будет намного проще, если вы уже знакомы с CSS-анимациями.

В CSS вы можете изменить монотонный темп анимации по умолчанию и указать произвольную функцию плавности, управляющую анимацией, с помощью свойства animation-timing-function. Функция плавности может быть одним из зарезервированных ключевых слов, либо кубической функцией Безье. Последние можно генерировать с помощью инструментов наподобие такого, созданного Лией Веру.

В SMIL темп анимации задается с помощью атрибута calcMode. По умолчанию темп линейный (linear) у всех анимационных элементов, кроме animateMotion (мы рассмотрим его далее в статье). Помимо значения linear, можно задавать такие значения: discrete, paced или spline.

  • discrete указывает, что функция анимации перескакивает с одного значения на следующее без какой-либо интерполяции. Это похоже на функцию steps() в CSS.
  • paced похоже на linear, за исключением того, что оно игнорирует любые промежуточные значения и временные метки, заданные с помощью keyTimes. Оно рассчитывает расстояния между последовательными значениями и соответственно делит их на время. Если все ваши значения находятся в линейном порядке, вы не заметите разницы. Но если они смещаются взад-вперед, или если это цвета (которые обрабатываются как значения трехмерных векторов), вы наверняка увидите промежуточные значения. Вот живой пример, представленный Амелией Беллами-Ройдз, показывающий разницу между тремя упомянутыми значениями calcMode (прим. перев.: в данном переводе используется переведенная версия оригинального примера Амелии):

    See the Pen SVG/SMIL calcMode comparison by Ilya Streltsyn (@SelenIT) on CodePen.

  • Четвертое значение, которое может принимать calcModespline. Оно строит интерполяцию от одного значения в списке values до следующего в соответствии с функции от времени, заданной кубическим сплайном Безье. Точки для сплайна задаются в атрибуте keyTimes, а управляющие точки для каждого интервала — в атрибуте keySplines.

Вероятно, вы заметили в последнем предложении новый атрибут: keySplines. Итак, что этот атрибут keySplines делает?

Снова обратимся к CSS-эквивалентам.

В CSS, вы можете указывать темп анимации внутри каждого ключевого кадра, вместо того, чтобы задавать темп всей анимации. Это дает лучший контроль над тем, как должна проходить анимация на каждом ключевом кадре. Пример использования этой возможности — создание эффекта «прыгающего мяча». Ключевые кадры для этого могут выглядеть так:

@keyframes bounce {
    0% {
        top: 0;
        animation-timing-function: ease-in;
    }
    15% {
        top: 200px;
        animation-timing-function: ease-out;
    }
    30% {
        top: 70px;
        animation-timing-function: ease-in;
    }
    45% {
        top: 200px;
        animation-timing-function: ease-out;
    }
    60% {
        top: 120px;
        animation-timing-function: ease-in;
    }
    75% {
        top: 200px;
        animation-timing-function: ease-out;
    }
    90% {
        top: 170px;
        animation-timing-function: ease-in;
    }
    100% {
        top: 200px;
        animation-timing-function: ease-out;
    }
}

Вместо ключевых слов для функций плавности мы могли задействовать соответствующие кубические функции Безье:

  • ease-in = cubic-bezier(0.47, 0, 0.745, 0.715)
  • ease-out = cubic-bezier(0.39, 0.575, 0.565, 1)

Давайте начнем с указания ключевых моментов времени и списка значений для нашего оранжевого круга, к которому будет применен такой же эффект «прыгания»:

<animate 
    xlink:href="#orange-circle"
    attributeName="cy"
    from="50"
    to="250" 
    dur="3s"
    begin="click"
    values="50; 250; 120;250; 170; 250; 210; 250"
    keyTimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
    fill="freeze" 
    id="circ-anim" />

Анимация начинается по клику и застывает, как только достигнет конечного значения. Следующим шагом, чтобы указать темп для каждого ключевого кадра, мы собираемся добавить атрибут keySplines.

Атрибут keySplines принимает набор управляющих точек Безье, которые ставятся в соответствие списку keyTimes, определяя тем самым кубическую кривую Безье для управления темпом анимации интервала. Каждое описание управляющих точек представляет собой набор из четырех значений x1 y1 x2 y2, описывающий управляющие точки Безье для одного временного отрезка. Все значения должны быть в диапазоне от 0 до 1, и если атрибуту calcMode не задано значение spline, то атрибут keySplines игнорируется.

Вместо того, чтобы принимать кубические функции Безье в качестве значений, keySplines принимает координаты двух управляющих точек, используемых для построения кривой. Управляющие точки показаны на следующем скриншоте генератора Лии. Скриншот показывает также координаты каждой точки, обозначенные тем же цветом, что сама точка. Для атрибута keySplines мы будем использовать именно эти значения, чтобы определить темп анимации ключевого кадра.

SMIL позволяет разделять эти значения либо запятыми с необязательным пробелом после, либо только пробелами. Значения keyTimes, определяющие соответствующий сегмент, являются «опорными точками» Безье, а значения keySplines — управляющие точки. Поэтому набор управляющих точек должен быть меньше, чем количество значений keyTimes (другими словами, keyTimes задает границы интервалов, а keySplines задаются для самих интервалов, поэтому в первом наборе оказыватся на один элемент больше — прим. перев.)

Если мы вернемся к примеру с «прыгающим мячиком», координаты управляющих точек для функций ease-in и ease-out показаны на следующих рисунках:

Таким образом, чтобы перевести это в анимационный SVG-элемент, мы получим следующий код:

<animate 
    xlink:href="#orange-circle"
    attributeName="cy"
    from="50"
    to="250" 
    dur="3s"
    begin="click"
    values="50; 250; 120;250; 170; 250; 210; 250"
    keyTimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
    keySplines=".42 0 1 1;
                0 0 .59 1;
                .42 0 1 1;
                0 0 .59 1;
                .42 0 1 1;
                0 0 .59 1;
                .42 0 1 1;
                0 0 .59 1;"
    fill="freeze" 
    id="circ-anim"/>

Вот живой демо-пример:

Просмотреть пример вживую на CodePen.

Если вы хотите задать лишь единую функцию плавности для всей анимации без каких-либо промежуточных значений, вам всё равно придется задавать ключевые кадры с помощью атрибута keyTimes, но понадобятся только начальный и конечный кадры, т.е. 0; 1, и никаких промежуточных values.

Относительные и накопительные анимации: additive и accumulate

Иногда бывает полезно определить анимацию, начинающуюся с того места, на котором предыдущая анимация закончилась, или такую, которая использует суммарный накопленный результат предыдущих анимаций как стартовое значение. Для этого в SVG есть два удобно названных атрибута: additive и accumulate.

Предположим, у вас есть элемент, который должен «расти» в ширину, или линия, длина которой должна увеличиваться, или элемент, который должен перемещаться с места на место поэтапно. Это особенно полезно для повторяющихся анимаций.

Как для любой другой анимации, вы зададите значения from и to. Однако, когда вы зададите для additive значение sum, каждое из этих значений будет считаться относительно исходного значения анимируемого атрибута.

Вернемся к нашему кругу. Для него исходная позиция cx равна 50. Если задать from="0" to="100", то ноль на самом деле означает исходные 50, а 100 на самом деле означает 50 + 100; другими словами, практически это как бы задает from="50" to="150".

Это дает нам следующий результат:

Просмотреть пример вживую на CodePen.

Это всё, что делает атрибут additive. Он просто указывает, должны ли значения from и to отсчитываться относительно текущего значения, или нет. Атрибут принимает лишь одно из двух значений: sum и replace. Последнее является значением по умолчанию, и по сути значит, что значения from and to заменяют текущее/исходное значение, что может привести к неприятному скачку перед началом анимации (попробуйте заменить sum на replace в примере выше, чтобы сравнить наглядно).

Однако, что если мы хотим добавлять значения так, чтобы второе повторение начиналось с конечного значения предыдущего? Здесь в игру вступает атрибут accumulate.

Атрибут accumulate отвечает за то, является ли анимация накопительной. По умолчанию его значение none, что означает, что при повторении, например, анимация стартует заново с начала. Но вы можете задать ему значение sum, что указывает, что каждая следующая итерация анимации основывается на конечном значении предыдущей итерации.

Так, если мы вернемся к предыдущей анимации и зададим accumulate="sum", мы получим следующий результат, которого мы и добивались:

Просмотреть пример вживую на CodePen.

Обратите внимание, что атрибут accumulate игнорируется, если целевой атрибут не поддерживает сложения значений, или если анимация не повторяется. Он также будет игнорироваться, если функция анимации задана только с атрибутом to.

Указание времени конца анимации с помощью end

Помимо указания, когда анимация начнется, вы можете также указать, когда она закончится, с помощью атрибута end. Например, вы можете установить анимацию на бесконечное повторение, а затем остановить ее, когда начнется анимация другого элемента. Атрибут end принимает значения, похожие на те, что принимает атрибут begin. Вы можете задавать абсолютные или относительные значения/смещения времени, количество повторений, значения событий и т.д.
Например, в следующем примере, оранжевый круг медленно движется с периодом в 30 секунд к противоположной стороне холста. Зеленый круг тоже анимируется, но только после клика. Анимация оранжевого круга прекращается, когда начинается анимация зеленого. Кликните по зеленому кругу, чтобы посмотреть, как оранжевый остановится:

Просмотреть пример вживую на CodePen.

Разумеется, такая же синхронизация анимаций возможна и для анимаций, применяемых к одному и тому же элементу. Например, предположим, мы установили анимацию цвета для круга, бесконечно меняющую его с одного значения на другое. Затем, когда по элементу кликнули, он движется к противоположной стороне. Теперь мы можем установить, чтобы анимация цвета остановилась, как только по элементу кликнули и анимация движения запустилась.

Просмотреть пример вживую на CodePen.

Задание интервалов анимации с помощью нескольких значений begin и end

Действительно, оба атрибута begin и end принимают список значений, разделенных точками с запятой. Каждое значение атрибута begin соответствует одному значению атрибута end, формируя таким образом активные и неактивные интервалы анимации.

Вы можете представить это как что-то вроде движущейся машины, где у колес машины бывают периоды «активности» и «неактивности», в зависимости от того, движется машина или нет. Вы даже можете сделать анимированный эффект движущейся машины, применив две анимации: одну, которая перемещает машину по координатной оси или некой траектории, причем эта анимация является относительной и накопительной, и вторую — которая вращает колеса машины в периоды, синхронизированные с движением машины.

Пример указания множественных начальных и конечных значений времени (т.е. интервалов) дан в следующем демо, где прямоугольник вращается на основе заданных интервалов, соответственно чередуя периоды активности и неактивности (перезапустите демо, если вы пропустили анимацию).

Просмотреть пример вживую на CodePen.

Обратите внимание, что в примере выше я использовала элемент <animateTransform> для вращения элемента вокруг центра. Мы поговорим об этом элементе подробнее в нижеследующих разделах.

Заметьте также, что даже если вы зададите repeatCount значение indefinite, оно будет перекрыто значениями end анимация не будет повторяться бесконечно.

Ограничение активной длительности элемента с помощью min и max

Точно так же, как вы можете ограничить время повторения анимации, вы можете ограничить и время активной длительности анимации. Атрибуты min и max указывают, соответственно, минимальную и максимальную активную длительность анимационного элемента. Они дают возможность задавать активной длительности элемента верхний и нижний пределы. Значениями обоих атрибутов могут быть часовые значения.

Для min это указывает минимальную величину активной длительности, измеряемую во время активности элемента. Значение должно быть больше либо равно 0, который является значением по умолчанию и вообще не ограничивает активную длительность.

Для max часовое значение указывает максимальную величину активной длительности, измеряемую во время активности элемента. Значение также должно быть больше 0. По умолчанию max имеет значение indefinite. Оно вообще не ограничивает активную длительность.

Если заданы оба атрибута min и max, то значение max должно быть больше или равно значению min. Если это требование не выполняется, оба атрибута игнорируются.

Но что определяет активную длительность элемента? Мы уже упоминали длительность повторения, в дополнение к «простой длительности», которая является длительностью анимации без повторений (задаваемой с помощью dur), так как это всё работает вместе? Что перекрывает что? И как быть с атрибутом end, который окажется «главным» и просто завершит анимацию?

Порядок такой: сначала браузер вычисляет активную длительность, исходя из значений dur, repeatCount, repeatDur и end. Затем он сравнивает результат вычисления с заданными значениями min и max. Если результат находится в этих пределах, вычисленное на первом шаге значение считается верным и не меняется. Иначе, возможны две ситуации:

  • Если первоначально вычисленная длительность превышает значение max, активная длительность элемента становится равной значению max.
  • Если первоначально вычисленная длительность меньше значения min, активная длительность элемента становится равной значению min, и элемент ведет себя следующим образом:
    • Если длительность повторения (либо простая длительность, если анимация не повторяется) элемента больше чем min, то элемент проигрывается нормально в течение активной длительности, ограниченной min.
    • Иначе, элемент проигрывается нормально в течение его длительности повторения (либо простой длительности, если элемент не повторяется), а затем «застывает» или не отображается в зависимости от значения атрибута fill.

Это помогает нам понять, как браузер фактически рассчитывает активную длительность. Ради краткости я не буду вдаваться здесь в подробности этого. Но в спецификации есть весьма исчерпывающая таблица с различными комбинациями атрибутов dur, repeatCount, repeatDur и end, которая показывает, чему будет равна активная длительность в случае каждой комбинации. Вы можете ознакомиться с таблицей и узнать больше подробностей в этом разделе спецификации.

Наконец, если анимационный элемент должен начинаться раньше его родительского элемента (напр., с отрицательным значением смещения), минимальная длительность отмеряется от вычисленного, а не наблюдаемого начала анимации. Это значит, что значение min может не давать видимого эффекта.

Пример <animate>: морфинг контуров

Одним из атрибутов, который можно анимировать в SMIL, но не в CSS, является атрибут d (сокращение от data, т.е. «данные») SVG-элемента <path>. Атрибут d содержит данные, определяющие контур формы, которую вы рисуете. Данные контура содержат набор команд и координат, которые сообщают браузеру, где и как рисовать точки, кривые и отрезки, образующие итоговый контур.

Анимация этого атрибута делает возможным морфинг SVG-контуров и создание эффектов плавного «перетекания» одной формы в другую. Но для того, чтобы изменять форму пути было возможно, начальная, конечная и любая из промежуточных форм должны иметь строго одинаковое количество вершин/ключевых точек, и порядок их должен оставаться тем же. Если число вершин не совпадает, анимация не заработает. Причина этого в том, что изменение формы на самом деле происходит путем перемещения вершин и интерполяции их положения, так что если одна вершина отсутствует или не совпадает, интерполировать оказывается нечего.

Чтобы анимировать SVG-контур, нужно задать для attributeName значение d, а затем указать для from и to значения, соответствующие начальной и конечной формам, также вы можете использовать атрибут values для задания любых промежуточных форм, через которые форма должна пройти в ходе изменения.

Ради краткости, я не буду вдаваться тут в подробности, как это делается. Вместо этого вы можете прочитать великолепную статью Ноа Блона, в которой он объясняет, как создавал меняющую форму анимацию как-бы-загрузки с помощью <animate>. Вот живой пример к статье Ноа:

Просмотреть пример вживую на CodePen.

А вот еще один пример морфинга, созданный Феликсом Хорнойю:

See the Pen SVG Countdown by Felix Hornoiu (@felixhornoiu) on CodePen.

Вы можете даже менять форму контура, используемого как маска для обрезки! Пример этого, созданный Хизер Бачел:

See the Pen Loading Animation with Morphing SVG! by Heather Buchel (@hbuchel) on CodePen.

Анимация по произвольной траектории: элемент <animateMotion>

Элемент <animateMotion> — мой любимый анимационный элемент SMIL. Его можно использовать для движения элемента вдоль траектории. Вы задаете траекторию движения одним из двух способов, которые мы сейчас рассмотрим, и затем настраиваете элемент, чтобы он двигался вдоль этой траектории.

Элемент <animateMotion> принимает те же атрибуты, что мы рассмотрели ранее, плюс три новых: keyPoints, rotate и path. Также есть одно отличие у атрибута calcMode, который по умолчанию для <animateMotion> имеет значение paced, а не linear.

Задание траектории движения с помощью атрибута path

Атрибут path задает траекторию движения. Он задается в том же формате и интерпретируется тем же образом, что атрибут d элемента path. Эффект анимации движения по траектории заключается в наложении дополнительной матрицы трансформации поверх текущей матрицы трансформации объекта, вызывающей сдвиг по осям X и Y в текущей системе координат пользователя на значения X и Y, рассчитываемые для каждого момента времени. Другими словами, заданная траектория отсчитывается относительно текущего положения элемента, используя данные траектории для перемещения объекта на позицию на этой траектории.

Для нашего круга мы зададим анимацию по траектории, которая будет выглядеть примерно так:

Вот код, требующийся для движения круга по этой траектории:

<animateMotion 
    xlink:href="#circle"
    dur="1s"
    begin="click"
    fill="freeze"
    path="M0,0c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4 c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4 c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5" />

Я хочу особо обратить ваше внимание на координаты в данных пути. Путь начинается с перемещения (M) в точку с координатами (0, 0), прежде чем начать рисовать кривую (c) в другую точку. Важно отметить, что точка (0, 0) в данном случае — это позиция нашего круга, неважно, где он находится, а НЕ верхний левый угол системы координат. Как я отметила выше, координаты в атрибуте path отсчитываются относительно текущего положения элемента!

Результат вышеприведенного кода следующий:

Просмотреть пример вживую на CodePen.

Если вы укажете путь, начинающийся с другой точки, а не (0, 0), круг рывком перескочит на расстояние, заданное координатами начальной точки. Например, предположим, что вы рисуете путь в Illustrator с последующим экспортом его в виде траектории движения (так я делала это в первый раз). Экспортированный путь будет выглядеть как-то так:

<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M100.4,102.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4 c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4 c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5"/>

Начальной точкой пути в этом случае будет (100.4, 102.2). Если бы мы использовали этот путь как траекторию движения, круг сначала «прыгнул» бы на примерно 100 единиц вправо и 102 единицы вниз, и лишь затем начал бы движение относительно нового положения. Так что не забывайте об этом при подготовке траекторий для ваших анимаций.

Если используются атрибуты from, by, to и values, то они указывают форму на текущем холсте, представляющую собой траекторию движения.

Указание траектории движения с помощью элемента <mpath>

Есть и другой способ задания траектории движения. Вместо использования относительного атрибута path, можно ссылаться на внешний путь с помощью элемента <mpath>. Элемент <mpath>, дочерний по отношению к элементу <animateMotion>, будет указывать на внешний путь с помощью атрибута xlink:href.

<animateMotion xlink:href="#circle" dur="1s" begin="click" fill="freeze">
  <mpath xlink:href="#motionPath" />
</animateMotion>

Элемент <path> с траекторией движения может быть указан в любом месте документа; он даже может быть буквально лишь объявлен внутри элемента <defs> и вообще не отображаться на холсте. В следующем примере траектория отображается, потому что в большинстве случаев вы захотите показать путь, по которому элемент движется.

Заметьте, что, согласно спецификации:

Различные точки (x, y) формы предоставляют вспомогательные матрицы трансформации поверх текущей матрицы трансформации для указанного объекта, которые вызывают смещение по осям X и Y в текущей пользовательской системе координат на значение (x,y) формы, вычисляемое в зависимости от времени. Таким образом, указанный объект сдвигается с течением времени на смещение траектории движения относительно начала текущей пользовательской системы координат. Вспомогательные матрицы трансформации применяются поверх любых трансформаций, задаваемых свойством transform целевого элемента, и любых анимаций этого атрибута, заданных элементами animateTransform для целевого элемента.

Опять же, позиция круга «умножается» или «трансформируется» на координаты из данных пути.

В следующем примере, у нас есть путь посередине холста. Круг позиционируется в начало пути. Но всё же, когда движение по траектории используется, круг не начинает движение из текущего положения. Смотрите пример для лучшего объяснения. Кликните по кругу, чтобы анимировать его.

Просмотреть пример вживую на CodePen.

Видите, как круг движется по тому же самому контуру, но в другом месте? Это потому, что позиция круга трансформируется (сдвигается) на значения из данных траектории.

Один способ обойти это — начинать с положения круга в точке (0, 0), так что трансформация по данным пути передвинет его куда нужно, и всё заработает как ожидалось.

Другой способ — применить трансформацию, которая «обнулит» координаты круга перед тем, как задействовать траекторию.

Вот модифицированная версия примера выше, использующая замкнутую траекторию и бесконечно зацикленную анимацию.

Просмотреть пример вживую на CodePen.

Правила приоритета для <animateMotion>

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

Правила перекрытия для animateMotion таковы:

  • При задании траектории движения, элемент mpath перекрывает атрибут path, который перекрывает values, а тот перекрывает from, by и to.
  • При определении точек, соответствующих атрибуту keyTimes, атрибут keyPoints перекрывает path, который перекрывает values, а тот перекрывает from, by и to.

Задание ориентации элемента относительно траектории движения rotate

В нашем предыдущем примере, элемент, который мы анимировали вдоль траектории, был кругом. Но что, если бы мы анимировали элемент, для которого важна ориентация, например, иконку машинки? Иконка машинки в следующем примере создана Freepik.

В этом примере я заменила круг группой с ID, равным «car», содержащей элемент, образующий группу. Затем, чтобы избежать вышеописанных проблем с движением по траектории, я применила трансформацию, сдвигающую машинку в определенную точку, так что в итоге начальной позицией оказывается точка (0, 0). Значения внутри трансформации на самом деле являются координатами точки, где начинает рисоваться первый контур машинки (сразу после команды M).

Затем машинка начинает двигаться по траектории. Но… вот как выглядит это движение:

Просмотреть пример вживую на CodePen.

Ориентация машинки фиксирована, и не соответствует направлению движения. Чтобы изменить это, мы будем использовать атрибут rotate.

У атрибута rotate может быть одно из трех значений:

  • auto: указывает, что объект будет поворачиваться с течением времени на угол направления (т.е. по касательному вектору направления) траектории движения.
  • auto-reverse: указывает, что объект будет поворачиваться с течением времени на угол направления (т.е. по касательному вектору направления) траектории движения плюс 180 градусов.
  • число: указывает, что к целевому элементу применена постоянная трансформация вращения, указанное число задает угол поворота в градусах.

Чтобы исправить ориентацию машинки в примере выше, мы начнем с задания вращению значения auto. Получим вот такой результат:

Просмотреть пример вживую на CodePen.

Если вы хотите, чтобы машинка «ездила» снаружи контура, значение auto-reverse решает задачу.

Просмотреть пример вживую на CodePen.

Это выглядит лучше, но у нас осталась проблема: машинка выглядит так, будто ездит по траектории задним ходом! Чтобы изменить это, нам придется перевернуть ее по ее оси Y. Это можно сделать путем масштабирования ее по этой оси с коэффициентом -1. Так что, если мы применим трансформацию к g с ID, равным car, машинка «поедет» вперед, как задумано. Трансформация масштабирования просто добавляется последовательно с ранее примененной трансформацией сдвига.

<g id="car" transform="scale (-1, 1) translate(-234.4, -182.8)">

И окончательный демо-пример выглядит так:

Просмотреть пример вживую на CodePen.

Управление расстоянием, пройденным анимацией по траектории, с помощью keyPoints

Атрибут keyPoints дает возможность указывать продвижение по траектории для каждого значения, указанного в keyTimes. Если он задан, keyPoints заставляет keyTimes применяться к значениям из keyPoints, а не к точкам, перечисленным в атрибуте values или точкам в атрибуте path.

keyPoints принимает разделенный точками с запятой список значений с плавающей точкой от 0 до 1 и указывает, как далеко по траектории должен продвинуться объект в момент времени, заданный в соответствующем значении keyTimes. Расчет расстояния определяется алгоритмами браузера. Каждое значение продвижения в списке соответствует одному значению из списка в атрибуте keyTimes. Если задан список keyPoints, в нем должно быть ровно столько же значений, сколько в списке keyTimes.

Важный момент, который здесь надо отметить — задание значения linear для calcMode, чтобы keyPoints заработал. Кажется, что по логике он должен работать и со значением paced, если ваши ключевые точки смещены то взад, то вперед, но с ним он не работает.

Вот пример Амелии Беллами-Ройдз (чей профиль на Codepen вам стоило бы изучить полностью), использующий keyPoints для имитации поведения, при котором движение по траектории начинается с предустановленным отступом, поскольку по умолчанию в SMIL у нас сейчас нет такой возможности (прим. перев.: здесь тоже оригинальный пример заменен переведенным вариантом).

See the Pen Motion along a closed path, arbitrary start point by Ilya Streltsyn (@SelenIT) on CodePen.

Движение текста по произвольной траектории

Движение текста по произвольной траектории отличается от движения других SVG-элементов по траекториям. Для анимации текста вам понадобится элемент <animate>, а не элемент <animateMotion>.

Во-первых, начнем с позиционирования текста вдоль траектории. Это можно сделать, вложив элемент <textPath> внутрь элемента <text>. Текст, который будет размещен вдоль траектории, будет определен внутри элемента <textPath>, а не как непосредственный потомок элемента <text>.

Затем textPath должен сослаться на фактический путь, который мы хотим использовать, в точности как в предыдущих примерах. Путь, на который мы ссылаемся, тоже может либо отображаться на холсте, либо определяться внутри <defs>. Посмотрите код следующего демо-примера.

Просмотреть пример вживую на CodePen.

Чтобы анимировать текст вдоль траектории, мы используем элемент <animate> для анимации атрибута startOffset.

startOffset представляет собой отступ текста относительно пути. 0% — начало пути; 100% соответствуют его концу. Так что если, например, задать отступ в 50%, текст будет начинаться посередине траектории. Думаю, вы уже догадались, что это нам дает.

Анимируя startOffset, мы создадим эффект текста, движущегося по траектории. Взгляните на код следующего демо.

Просмотреть пример вживую на CodePen.

Анимация трансформаций: элемент <animateTransform>

Элемент <animateTransform> анимирует атрибут трансформации целевого элемента, тем самым позволяя анимации управлять сдвигом, масштабом, вращением и/или наклоном. Он принимает те же атрибуты, что элемент <animate>, плюс один добавочный атрибут: type.

Атрибут type служит для указания типа трансформации, которую анимируют. У него может быть одно из пяти значений: translate, scale, rotate, skewX и skewY.

Атрибуты from, by и to принимают значения в том же синтаксисе, который подходит для данного типа трансформации:

  • Для type="translate" каждое отдельное значение выражается как <tx> [,<ty>]</ty> (сдвиг по каждой оси).
  • Для type="scale" каждое отдельное значение выражается как <sx> [,<sy>]</sy> (масштаб по каждой оси).
  • Для type="rotate" каждое отдельное значение выражается как <rotate-angle> [<cx> <cy>]</cy></cx> (угол поворота и координаты центра вращения).
  • Для type="skewX" and type="skewY" каждое отдельное значение выражается как <skew-angle> (угол наклона).

     

Если вы не очень знакомы с функциями SVG-атрибута transform, то ради краткости этой статьи, а также потому, что подробности его синтаксиса и работы выходят далеко за рамки этой статьи, я советую вам прочитать статью о них, которую я написала чуть раньше: «Разбираемся с системами координат и трансформациями в SVG (часть 2): атрибут transform» — прежде чем продолжать изучение этого руководства.

Вернемся к предыдущему примеру, где мы вращали розовый прямоугольник с помощью элемента <animateTransform>. Код для вращения выглядит так:

<rect id="deepPink-rectangle" width="50" height="50" x="50" y="50" fill="deepPink" />

  <animateTransform 
      xlink:href="#deepPink-rectangle"
      attributeName="transform" 
      attributeType="XML"
      type="rotate"
      from="0 75 75"
      to="360 75 75" 
      dur="2s"
      begin="0s"
      repeatCount="indefinite"
      fill="freeze" 
      />

Атрибуты from и to указывают угол поворота (начальный и конечный) и центр вращения. Конечно, в них обоих центр вращения остается тем же самым. Если вы не укажете центр, им станет верхний левый угол SVG-холста. Вот живой пример для вышеприведенного кода:

Просмотреть пример вживую на CodePen.

Вот еще один забавный пример с одним animateTransform за авторством Габриэля:

See the Pen Orbit by Gabriel (@guerreiro) on CodePen.

Анимация одной отдельной трансформации очень проста, однако, всё может стать куда сложнее и запутаннее, когда дело дойдет до множественных трансформаций, особенно с учетом того, что один animateTransform может перекрывать другой, так что вместо наложения или последовательного применения эффектов вы можете получить полную противоположность. Так уж устроены системы координат и трансформации в SVG (снова рекомендую обратиться к ранее упомянутой статье). Примеров масса, но они выходят за рамки статьи. Для трансформирования SVG-графики я советую использовать CSS-трансформации. Браузеры вовсю стараются, чтобы те идеально работали с SVG, так что, возможно, вам вообще не понадобится никакой SMIL для анимации трансформаций в SVG.

Элемент <set>

Элемент set предоставляет простое средство для задания значения атрибуту на определенный отрезок времени. Он поддерживает все типы атрибутов, включая те, которые по своей логике не могут быть интерполированы, напр. строки и булевы значения. Элемент set не поддерживает относительных/накопительных анимации: относительные и накопительные атрибуты недопустимы и игнорируются, даже если указаны.

Поскольку <set> используется для выставления элемента в определенное значение в определенный момент и отрезок времени, для него доступны не все атрибуты, упомянутые для предыдущих анимационных элементов. Например, у него нет атрибутов from или by, потому что изменяемое значение не меняется со временем постепенно.

Для set вы можете указать целевой элемент, имя и тип атрибута, значение to и время анимации, которым можно управлять с помощью следующих атрибутов: begin, dur, end, min, max, restart, repeatCount, repeatDur и fill.

Вот пример, который меняет цвет вращающегося прямоугольника на синий, когда по нему кликают. Цвет остается синим в течение 3 секунд, а затем возвращается к исходному цвету. Каждый раз при клике по прямоугольнику запускается анимация set, и цвет меняется на три секунды.

Просмотреть пример вживую на CodePen.

Элементы, атрибуты и свойства, которые можно анимировать

Не все SVG-атрибуты можно анимировать, и не все из тех, которые можно анимировать, можно анимировать всеми анимационными элементами. За полным списком анимируемых атрибутов и таблицей, показывающей, какой из этих атрибутов можно анимировать каким элементом, пожалуйста, обратитесь к этому разделу спецификации SVG-анимаций.

В заключение

У SMIL огромный потенциал, и я едва затронула поверхность и лишь коснулась основ и технических моментов того, как он работает в SVG. Можно создать множество очень впечатляющих эффектов, особенно с морфингом и преобразованием форм. Предела фантазии нет. Не бойтесь экспериментировать! И не забывайте делиться тем, что делаете, с сообществом; мы будем счастливы видеть, чего вы смогли достичь. Спасибо за чтение!

Эта статья была обновлена по результатам дискуссии в комментариях. Спасибо за важные дополнения, Амелия. =)

P.S. Это тоже может быть интересно:

13 Комментарии

  1. Arseny

    SelenIT, скажите, пожалуйста, а сколько лет вы уже занимаетесь версткой?

    1. SelenIT (Автор записи)

      Достаточно давно. Но с SVG более-менее регулярно имею дело только последних пару лет, так что из статьи сам вынес массу нового:)

    2. Максим Усачев

      Столько не живут:)))

  2. Andy87

    Классная статья!
    Столько нового!!!
    Руки так и чешутся опробовать всё что тут увидел!

  3. Андрей

    Спасибо за статью!

    Ребят, подскажите пожалуйста как анимировать написание текста.

    Пример:

    Каждая линия должна анимироваться отдельно.

    Я уже нашел плагин walkway который работает с элементом path, нужен такой же эффект только для текста.

    Но я не знаю как узнать координаты точек, где их брать?

     

    1. Андрей

      Что-то изображение не вставилось. Вот ссылка на пример http://dropmefiles.com/qymrk

    2. SelenIT (Автор записи)

      На мой взгляд, для такой надписи логично перевести текст в path и дальше действовать этим плагином как обычно. Как вариант, нарисовать path поверх текста и анимировать его исчезновение (вот наскоро слепленная очень грубая иллюстрация принципа, правильнее, конечно, делать это через маски, но пример из статьи с анимацией маски у меня не заработал в Firefox).

      1. Андрей

        В том-то и загвоздка, я не знаю как перевести его в path. С илюстратором никогда не работал, удалось только преобразовать текст в кривые. При наведении на точки видны координаты типо X:40,40 Y:50,50, но это не те цифры как я понял.

        Наверое этот вопрос стоит задать на демиарте ).

        Возможно ли другими средствами это сделать?

        1. SelenIT (Автор записи)

          Кривые — это и есть paths после сохранения в SVG, по идее. Начальная точка каждой кривой будет прямо там в коде, сразу после буквы m/M в начале атрибута d.
           

          1. Андрей

            Не по идее, а вы правы! :). Спасибо, а то замучался уже. Я думал это можно как то узнать в самом илюстраторе.

  4. Arris

    У меня вот такая хитрая проблема:

    http://karel.wintersky.ru/experiments/svg/

    Опорная точка для анимации у дракончика — на самом кончике хвоста. Как можно эту опорную точку сдвинуть? На какое-нибудь колесо или педали.

    Сразу скажу: вращать исходник в инскейпе не помогло совершенно.

  5. Николай

    Chrome собирается отказаться от поддержки SMIL анимации https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/5o0yiO440LM/59rZqirUQNwJ

    1. SelenIT (Автор записи)

      Увы, есть такое. Наверное, сейчас логичнее смотреть в сторону Web Animations API (а в том же Chrome с недавних пор — правда, только за экспериментальным флагом — можно делать морфинг SVG-контуров и из CSS-анимации, хотя ни один стандарт пока такого не упоминает).

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.