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

Обзор

Графика SVG может быть анимирована с помощью элементов анимации. Эти элементы были первоначально определены в спецификации анимации SMIL; к ним относятся:

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

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

Расширения SVG включают:

  • <animateTransform> - позволяет задавать анимацию одному из атрибутов преобразований SVG за определенный промежуток времени, например для атрибута transform;
  • path (атрибут) - позволяет любой функции синтаксиса данных пути SVG быть указанной в атрибуте пути к элементу animateMotion (SMIL-анимация позволяет использовать в атрибуте пути только подчиненный набор синтаксиса данных пути SVG).

Мы поговорим об animateMotion в следующем разделе:

  • <mpath> - используется в сочетании с элементом animateMotion для ссылки на траекторию движения, которая должна использоваться, как и траектория движения. Элемент mpath включается внутри элемента animateMotion, перед закрывающим тегом;
  • keypoints (атрибут) - используется в качестве атрибута для animateMotion, чтобы обеспечить точный контроль скорости перемещения анимированного элемента вдоль траектории;
  • rotate (атрибут) - используется в качестве атрибута для animateMotion, чтобы контролировать поворачивается ли объект автоматически таким образом, чтобы его точки х-оси поворачивались в том же (или обратном) направлении, что и направление касательного вектора пути движения. Этот атрибут является ключом к созданию движения вдоль пути, которое будет работать так, как вам нужно. Подробнее об этом в разделе animateMotion.

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

Для чего используется SVG-анимация?

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

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

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

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

Если вы предпочитаете использовать JavaScript, я рекомендую вам snap.svg Дмитрия Барановского, который называют "jQuery для SVG".

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

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

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

Поддержка браузеров и альтернативные варианты

SMIL довольно широко поддерживается браузерами. Он работает во всех браузерах за исключением Internet Explorer и Opera Mini. Чтобы получить исчерпывающую информацию относительно поддержки браузерами, вы можете ознакомиться с таблицей совместимости или "Могу ли использовать".

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

Указание цели анимации с помощью 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> по оси х, вы делаете это, указав cx в качестве значения атрибута attributeName.

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

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

При указании имени атрибута, вы можете добавить префикс XMLNS (сокращение от пространство имен XML) для обозначения пространства имен атрибута.

Пространство имен также можно задать с помощью атрибута attributeType. Например, некоторые атрибуты принадлежат пространству имен CSS (что означает, что атрибут может быть также найден как свойство CSS), другие - только пространству имен XML.

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

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

Например, следующий фрагмент задает анимацию для непрозрачности прямоугольника 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 (который определяет положение его центра по оси х).

Для этого мы используем элемент <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 единиц по оси х.

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

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

Еще более интересно, что вы можете определить для атрибута begin такое значение, как click + 1s, которое будет запускать анимацию через одну секунду после клика мышью!

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

Атрибут dur аналогичен атрибуту CSS animation-duration.

Атрибуты from и to аналогичны кифреймам from и to блока анимации @keyframe CSS:

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

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

Его значения в SVG аналогичны тем, которые используются в CSS, за исключением использования различных имен:

  • freeze: Значение, которое указывает заморозить состояние эффекта на том, которое было при последнем значении продолжительности анимации. Эффект анимации "замораживается" до тех пор, пока документ не будет закрыт (или пока анимация не будет перезапущена);
  • remove: Эффект анимации удаляется (больше не применяется), по завершению действия анимации, заданного продолжительностью. После завершения анимации она больше не применяется к целевому элементу (если только анимация не будет перезапущена).

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

<svg width="500" height="100">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

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

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

Данный эффект заметен, в основном только когда вы изменяете продолжительность анимации от одного цикла к другому, подобно тому, как работает функция CSS steps().

В SVG эквивалентом функции CSS steps() является calcMode="discrete". Мы доберемся до атрибута calcMode="discrete" чуть позже.

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

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

Вы можете установить для него одно из трех возможных значений:

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

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

Предположим, мы хотим задать анимацию для положения и цвета круга, таким образом, чтобы изменение цвета происходило в конце анимации перемещения. Мы можем сделать это, установив значение begin для анимации изменения цвета равным длительности анимации перемещения; так мы, как правило, делаем в 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 секунду после начала анимации круга.

Вы можете увидеть все это в этой демонстрации:

HTML:

<svg width="500" height="350">
  <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" 
           id="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"/>

</svg>
<p>Нажмите на круг, чтобы запустить его анимацию и анимацию прямоугольника через 1 секунду вслед за ней.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

Вы также можете запустить анимацию прямоугольника по завершении анимации круга, используя событие 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.
HTML:

<svg width="500" height="350">
  <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="0s"
           repeatCount="2"
           fill="freeze" 
           id="circ-anim"/>

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

</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

В CSS свойство animation-direction определяет, должна ли анимация проигрываться в обратном направлении или каждый цикл стартует с нулевой точки.

Значение animation-direction: alternate означает, что четные циклы анимации проигрываются в обычном направлении, а нечетные воспроизводятся в обратном направлении. Это означает, что первый цикл будет проигрываться от начала до конца, а второй - с конца обратно к началу, третий цикл снова будет проигрываться от начала к концу и так далее.

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

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

HTML:

<svg width="500" height="350">
  <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="0s"
           repeatCount="2"
           fill="freeze" 
           id="circ-anim"/>

  <animate 
           xlink:href="#blue-rectangle"
           attributeName="x" 
           from="50"
           to="425"

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

Ограничение времени повторения анимации с помощью 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" />

А это демо, иллюстрирующее данный атрибут:
HTML:

<svg version="1.1" width="320" height="320" viewBox="0 0 320 320" fill="none" stroke="#000" stroke-linecap="round"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <path id="r1">
      <animate id="p1" attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin"/>
    </path>
    <path id="r2">
      <animate attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite" begin="p1.begin+1s"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin+1s"/>
    </path>
    <path id="r3">
      <animate attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite" begin="p1.begin+2s"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin+2s"/>
    </path>
    <path id="r4">
      <animate id="p1" attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite" begin="p1.begin+3s"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin+3s"/>
    </path>
    <path id="r5">
      <animate attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite" begin="p1.begin+4s"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin+4s"/>
    </path>
    <path id="r6">
      <animate attributeName="d" values="m160,160l0,0 0,0;m130,110l30,-17 30,17;m130,60l30,-17 30,17;m160,20l0,0 0,0" dur="6s" repeatCount="indefinite" begin="p1.begin+5s"/>
      <animate attributeName="stroke-width" values="0;4;4;4;0" dur="6s" repeatCount="indefinite" begin="p1.begin+5s"/>
    </path>
  </defs>
  <use xlink:href="#r1"/>
  <use xlink:href="#r1" transform="rotate(60 160 160)"/>
  <use xlink:href="#r1" transform="rotate(120 160 160)"/>
  <use xlink:href="#r1" transform="rotate(180 160 160)"/>
  <use xlink:href="#r1" transform="rotate(240 160 160)"/>
  <use xlink:href="#r1" transform="rotate(300 160 160)"/>
  <use xlink:href="#r2" transform="rotate(30 160 160)"/>
  <use xlink:href="#r2" transform="rotate(90 160 160)"/>
  <use xlink:href="#r2" transform="rotate(150 160 160)"/>
  <use xlink:href="#r2" transform="rotate(210 160 160)"/>
  <use xlink:href="#r2" transform="rotate(270 160 160)"/>
  <use xlink:href="#r2" transform="rotate(330 160 160)"/>
  <use xlink:href="#r3"/>
  <use xlink:href="#r3" transform="rotate(60 160 160)"/>
  <use xlink:href="#r3" transform="rotate(120 160 160)"/>
  <use xlink:href="#r3" transform="rotate(180 160 160)"/>
  <use xlink:href="#r3" transform="rotate(240 160 160)"/>
  <use xlink:href="#r3" transform="rotate(300 160 160)"/>
  <use xlink:href="#r4" transform="rotate(30 160 160)"/>
  <use xlink:href="#r4" transform="rotate(90 160 160)"/>
  <use xlink:href="#r4" transform="rotate(150 160 160)"/>
  <use xlink:href="#r4" transform="rotate(210 160 160)"/>
  <use xlink:href="#r4" transform="rotate(270 160 160)"/>
  <use xlink:href="#r4" transform="rotate(330 160 160)"/>
  <use xlink:href="#r5"/>
  <use xlink:href="#r5" transform="rotate(60 160 160)"/>
  <use xlink:href="#r5" transform="rotate(120 160 160)"/>
  <use xlink:href="#r5" transform="rotate(180 160 160)"/>
  <use xlink:href="#r5" transform="rotate(240 160 160)"/>
  <use xlink:href="#r5" transform="rotate(300 160 160)"/>
  <use xlink:href="#r6" transform="rotate(30 160 160)"/>
  <use xlink:href="#r6" transform="rotate(90 160 160)"/>
  <use xlink:href="#r6" transform="rotate(150 160 160)"/>
  <use xlink:href="#r6" transform="rotate(210 160 160)"/>
  <use xlink:href="#r6" transform="rotate(270 160 160)"/>
  <use xlink:href="#r6" transform="rotate(330 160 160)"/>
</svg>

Результат

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

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

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

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

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

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

Управление значениями кифреймов анимации: 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 (включительно), представляющее собой пропорциональный сдвиг по времени в проигрывании элемента анимации. Таким образом, keyTimes работает так же, как его аналоги в CSS, за исключением того, что мы определяем его в виде десятичной дроби, а не в процентах.

Ниже приведена демонстрация для данного кода. Нажмите на круг, чтобы запустить анимацию.
HTML:

<svg width="500" height="350">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

  <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"/>

</svg>
<p>Нажите на круг, чтобы запустить анимацию.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

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

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

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

Функция тайминга может определяться одним из нескольких предопределенных ключевых слов или функций cubic b;zier. Последняя может быть создана при помощи инструментов, наподобие этого, разработанного Леей Веру.

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

  • discrete указывает, что функция анимации переходит от одного значения к другому без интерполяции. Это похоже на функцию CSS steps();
  • paced похоже на значение linear, кроме того, что при этом игнорируются любые промежуточные значения, и изменение времени выполнения анимации определяется в keyTimes;
  • spline производит интерполяцию от одного значения в списке значений к другому в соответствии с функцией времени, определяемой кубическим сплайном Безьера. Точки сплайна определяются в атрибуте 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;
    }
}

Вместо функции корректировки мы можем использовать соответствующие функции cubic-bezier:

  • 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 принимает набор контрольных точек b;zier, связанных со списком keyTimes, определяющим кубическую функцию Безье, которая управляет интервалами с разным темпом. Значение атрибута представляет собой разделенный точками с запятой список описаний контрольных точек.

Каждое описание контрольной точки представляет собой набор из четырех значений: x1 y1 x2 y2, описывающий координаты контрольных точек b;zier для одного сегмента времени. Их значение должно находиться в диапазоне от 0 до 1, и для calcMode должно быть установлено значение spline, иначе атрибут игнорируется.

Вместо использования в качестве значений функции cubic-bezier, keySplines принимает координаты двух контрольных точек, которые используются для построения кривой. Данные контрольные точки вы можете видеть на приведенном ниже скриншоте, взятом из инструмента Леи.

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

В SMIL эти значения можно разделять либо запятыми с дополнительным пробелом, либо просто пробелами. Значения keyTimes, которые определяют соответствующий сегмент, являются "опорными точками" b;zier, а значения keySplines являются контрольными точками. Таким образом, контрольных точек должно быть задано на одну меньше, чем keyTimes:

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

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

Управление темпом анимации с помощью пользовательских корректив: calcMode и keySplines - 2

Таким образом, переведя все это в элемент анимации 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"/>

Ниже приводится демо-версия, но убедитесь, что вы запустили ее в Firefox, а не в Chrome, потому что в его последней версии найдена ошибка, из-за которой демо-версия не будет работать корректно:

HTML:

<div class="ball"></div>
<svg width="500" height="350">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

  <animate 
           xlink:href="#orange-circle"
           attributeName="cy"
           from="50"
           to="250" 
           dur="3s"
           begin="click"
           calcMode="spline"
           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="0.42 0 1 1;
                       0 0 0.59 1;
                       0.42 0 1 1;
                       0 0 0.59 1;
                       0.42 0 1 1;
                       0 0 0.59 1;
                       0.42 0 1 1;"
           fill="freeze"  
           id="circ-anim"/>

</svg>
<p>Нажите на круг, чтобы запустить анимацию.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

Если вы хотите указать только общую функцию корректировки скорости для всей анимации, без промежуточных значений, вы все равно должны указать кифреймы, используя атрибут 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".

Сделав это, мы получим следующий результат:

HTML:

<svg width="500" height="100">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

  <animate 
           xlink:href="#orange-circle"
           attributeName="cx"
           from="0"
           to="100" 
           additive="sum"
           repeatCount="3"
           calcMode="spline"
           keyTimes="0;1"
           keySplines=".42 0 1 1"
           dur="1s"
           begin="click"
           fill="freeze" />
</svg>
<p>Нажмите на круг, чтобы запустить анимацию.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

Это все, что делает атрибут additive. Он просто определяет, будут ли значения from и to связаны с текущим значением атрибута или нет. Атрибут принимает одно из двух значений: sum и replace.

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

(Попробуйте в приведенном выше примере заменить sum на replace , и вы увидите, что получится).

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

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

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

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

<svg width="500" height="100">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />

  <animate 
           xlink:href="#orange-circle"
           attributeName="cx"
           from="0"
           to="100" 
           additive="sum"
           accumulate="sum"
           repeatCount="3"
           calcMode="spline"
           keyTimes="0;1"
           keySplines=".42 0 1 1"
           dur="1s"
           begin="click"
           fill="freeze" />
</svg>
<p> Нажмите на круг, чтобы запустить анимацию.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

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

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

Атрибут end принимает значения, аналогичные тем, которые имеет атрибут begin. Вы можете указывать абсолютные или относительные значения времени / интервалы, значения повторений, значения событий и т.д.

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

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

HTML:

<svg width="500" height="350">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />
  <circle id="green-circle" r="30" cx="50" cy="150" fill="#009966" />

  <animate 
           xlink:href="#orange-circle"
           attributeName="cx"
           from="50"
           to="450" 
           dur="30s"
           begin="0s"
           end="gCircAnim.begin"
           fill="freeze" 
           id="oCircAnim"/>
  <animate 
           xlink:href="#green-circle"
           attributeName="cx"
           from="50"
           to="450" 
           dur="1s"
           begin="click"
           fill="freeze"
           id="gCircAnim"/>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

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

<svg width="500" height="100">
  <circle id="orange-circle" r="30" cx="50" cy="50" fill="#0099CC" />

  <animate 
           xlink:href="#orange-circle"
           attributeName="cx"
           from="50"
           to="450" 
           dur="1s"
           begin="click"
           fill="freeze" 
           id="move"/>
  <animate 
           xlink:href="#orange-circle"
           attributeName="fill"
           from="#0099CC"
           to="deepPink" 
           dur="5s"
           repeatCount="indefinite"
           begin="0s"
           end="move.begin"
           fill="freeze"
           id="changeColor"/>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

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

Это можно представить, как движущийся автомобиль, у которого колеса то активны, то не активны какое-то время, в зависимости от того, движется ли автомобиль или стоит на месте.

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

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

<svg width="500" height="150">
  <style>
    rect {
      -moz-transform-origin: 75px 75px;
      transform-origin: 50% 50%;
    }
  </style>
  <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; 5s; 9s; 17s;"
           end="2s; 8s; 15s; 25s;"
           fill="freeze" 
           restart="whenNotActive"
           />

</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

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

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

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

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

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

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

Если указаны оба атрибута, то значение 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), является атрибут из <path> SVG - d (сокращенно от "data" - данные).

Атрибут d содержит данные, которые определяют контуры формы или фигуры, которую вы создаете. Эти данные контура состоят из набора команд и координат, которые указывают браузеру, где и как рисовать точки, дуги и линии, которые составляют окончательный контур.

Анимируя этот атрибут, мы можем трансформировать контуры SVG и создавать эффекты метаморфоз фигур.

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

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

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

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

<img />

<svg viewbox="0 0 100 100">
  <path fill="#1EB287">
    <animate 
             attributeName="d" 
             dur="1440ms" 
             repeatCount="indefinite"
             keyTimes="0;
                       .0625;
                       .208333333;
                       .3125;
                       .395833333;
                       .645833333;
                       .833333333;
                       1;"
             calcMode="spline" 
             keySplines="0,0,1,1;
                         .42,0,.58,1;
                         .42,0,1,1;
                         0,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1"
             values="M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 50,0 
                     C 75,25 75,25 100,50 
                     75,75 75,75 50,100
                     25,75 25,75 0,50
                     25,25 25,25 50,0
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;

                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;

                     M 100,0 
                     C 100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     50,0 50,0 100,0
                     Z;"/>
    <animate 
             attributeName="fill"
             dur="1440ms" 
             repeatCount="indefinite"
             keyTimes="0;
                       .0625;
                       .208333333;
                       .3125;
                       .395833333;
                       .645833333;
                       .833333333;
                       1;"
             calcMode="spline" 
             keySplines="0,0,1,1;
                         .42,0,.58,1;
                         .42,0,1,1;
                         0,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1"
             values="#1eb287;
                     #1eb287;
                     #1ca69e;
                     #188fc2;
                     #188fc2;
                     #bb625e;
                     #ca5f52;
                     #1eb287;"/>
  </path>
</svg>

CSS:

body {
  height: 100vh;
  text-align: center;
  box-sizing:  border-box;
  padding-top: calc(50vh - 56px);
}

img, svg {
  display: inline-block;
  vertical-align: middle;
}

svg {
  height: 38px;
  width: 38px;
  display: inline-block;
}

JS:

// Injecting the image helps keep them in sync
$(document).ready(function(){
  $('img').attr('src', 'http://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2014/08/1407310358spinner-cropped.gif');
});

Результат

А вот еще один пример морфинга Феликса Орнуа:
HTML:

<div class=cont ><svg viewbox="0 0 400 400">
  <path fill="#000">
    <animate 
      attributeName="d" 
      dur="12000ms" 
      repeatCount="indefinite"
      keyTimes="0;0.0388; 0.1176; 0.1564; 0.2352; 0.274; 0.3528; 0.3916; 0.4704; 0.5092; 0.588; 0.6268; 0.7056; 0.7444; 0.8232; 0.862; 0.9408;
                1;"

      values="
        M 68,0 
         52,15.899 0,47 12,212 47,251.793 120,207 110,325.793 78,328.793 57,296.793 10,318.793 
	23.239,408.725 43.143,421.587 173,390 188,353 179,151 177.959,96.168 122,87 111,149 82,146 84,88 177,113 181.843,28 117,0,0  
        Z;M 68,0 
         52,15.899 0,47 12,212 47,251.793 120,207 110,325.793 78,328.793 57,296.793 10,318.793 
	23.239,408.725 43.143,421.587 173,390 188,353 179,151 177.959,96.168 122,87 111,149 82,146 84,88 177,113 181.843,28 117,0,0  
        Z;

              M 78,0 
         15,31.587 0,68.587 25,229.587 7,247.587 6,324.587 66,334.587 77,272.587 106,275.587 104,333.587 
	6.959,307.755 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 68,214.587 72,137.587 107,124.587 107.535,227.573 
	156.882,222.671 164.761,12.862 144.857,0,0  
        Z;M 78,0 
         15,31.587 0,68.587 25,229.587 7,247.587 6,324.587 66,334.587 77,272.587 106,275.587 104,333.587 
	6.959,307.755 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 68,214.587 72,137.587 107,124.587 107.535,227.573 
	156.882,222.671 164.761,12.862 144.857,0,0  
        Z;
             M 78,0 114.167,0 24.667,18.725 0,42.725 0,112.725 14.667,126.725 73.333,109.392 95.333,107.392 
	107.333,126.725 74,260.725 57.333,318.725 49.333,364.726 54,384.726 95.333,402.059 122,398.059 147.333,259.392 163.333,187.392 
	168.934,166.671 174,142.725 185.333,80.059 198,18.725 190.667,4.725 163.333,0;M 78,0 114.167,0 24.667,18.725 0,42.725 0,112.725 14.667,126.725 73.333,109.392 95.333,107.392 
	107.333,126.725 74,260.725 57.333,318.725 49.333,364.726 54,384.726 95.333,402.059 122,398.059 147.333,259.392 163.333,187.392 
	168.934,166.671 174,142.725 185.333,80.059 198,18.725 190.667,4.725 163.333,0 ;

              M 100,5 144.857,0 15,31.587 0,68.587 9,270.587 10.041,325.418 66,334.587 77,272.587 106,275.587 
	104,333.587 11,308.587 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 139,187.587 68,214.587 72,137.587 
	107,124.587 128,156.587 175,134.587 164.761,12.862;M 100,5 144.857,0 15,31.587 0,68.587 9,270.587 10.041,325.418 66,334.587 77,272.587 106,275.587 
	104,333.587 11,308.587 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 139,187.587 68,214.587 72,137.587 
	107,124.587 128,156.587 175,134.587 164.761,12.862;

              M 100,0 28.843,20 0,34 0,202 11.843,279 27.843,292 70.843,277 99.843,280 97.843,338 50.843,337 0,398 
	55.843,438 105.843,431 162.843,378 179.843,294 173.843,214 135.843,187 86.921,195 67.843,161 72.843,125 165.843,109 179.843,31 
	156.843,0;M 100,0 28.843,20 0,34 0,202 11.843,279 27.843,292 70.843,277 99.843,280 97.843,338 50.843,337 0,398 
	55.843,438 105.843,431 162.843,378 179.843,294 173.843,214 135.843,187 86.921,195 67.843,161 72.843,125 165.843,109 179.843,31 
	156.843,0;

              M 100,0 1.99,42.63 9.489,221.121 0,305.117 41.998,323.117 101.994,311.117 103.843,332 107.843,363 
	105.843,394 109.843,406 127.843,406 160.843,399.101 173.843,391 173.843,357 173.843,18 165.843,0 95.843,11 103.843,223 
	86.843,232 73.182,228.376 67.986,155.124 68.997,23.132 47.998,23.132 ;M 100,0 1.99,42.63 9.489,221.121 0,305.117 41.998,323.117 101.994,311.117 103.843,332 107.843,363 
	105.843,394 109.843,406 127.843,406 160.843,399.101 173.843,391 173.843,357 173.843,18 165.843,0 95.843,11 103.843,223 
	86.843,232 73.182,228.376 67.986,155.124 68.997,23.132 47.998,23.132 ;

              M 100,0 3.216,55.855 41.341,131 106.007,117.667 104.674,156.333 47.341,181 26.674,203 35.341,268.333 
	96.007,267 95.341,314.333 78.674,333.667 31.508,328.5 0,391.5 101.007,423 177.507,368 172.507,236 142.507,226.5 136.507,208 
	159.007,197 172.507,181.5 177.507,36.5 152.507,11 93.853,0 ;M 100,0 3.216,55.855 41.341,131 106.007,117.667 104.674,156.333 47.341,181 26.674,203 35.341,268.333 
	96.007,267 95.341,314.333 78.674,333.667 31.508,328.5 0,391.5 101.007,423 177.507,368 172.507,236 142.507,226.5 136.507,208 
	159.007,197 172.507,181.5 177.507,36.5 152.507,11 93.853,0 ;

              M 100,0 44,16.587 1.204,55.672 0,158.587 0,200.587 13.676,209.587 53,199.587 69,139.587 100,139.587 
	94,191.587 39,292.587 0,362.587 34,429.587 108,405.688 163,405.688 175,373.587 153,332.587 115.5,337.587 96,331.587 
	164.51,210.793 164.51,119.587 151,6.587 108,0;M 100,0 44,16.587 1.204,55.672 0,158.587 0,200.587 13.676,209.587 53,199.587 69,139.587 100,139.587 
	94,191.587 39,292.587 0,362.587 34,429.587 108,405.688 163,405.688 175,373.587 153,332.587 115.5,337.587 96,331.587 
	164.51,210.793 164.51,119.587 151,6.587 108,0;

              M 100,0 0,62.189 10.809,86.189 36.809,128.189 62.309,121.189 73.058,122.499 73.058,122.499 
	74.809,136.189 73.319,189.189 70.319,344.188 23.319,359.188 57.319,426.188 131.318,402.29 186.318,402.29 198.318,370.188 
	176.318,329.188 138.818,334.188 132.389,332.21 130.336,308.766 139.318,116.189 145.318,3.189 104.991,0 62.094,35.625 ;M 100,0 0,62.189 10.809,86.189 36.809,128.189 62.309,121.189 73.058,122.499 73.058,122.499 
	74.809,136.189 73.319,189.189 70.319,344.188 23.319,359.188 57.319,426.188 131.318,402.29 186.318,402.29 198.318,370.188 
	176.318,329.188 138.818,334.188 132.389,332.21 130.336,308.766 139.318,116.189 145.318,3.189 104.991,0 62.094,35.625 ;"
            />
  </path>
  </svg> </div>

CSS:

* { box-sizing: border-box; }

.cont {
  height: 100vh;
  padding: 20vmin;
  -webkit-filter: contrast(10);
  background-color: white;
}

svg {
  width: 100%;
  height: 100%;
  opacity: 0.9;
  -webkit-filter: blur(3px);}

Результат

Вы даже можете использовать трансформирующийся контур для отсечения окружающего фона и обозначения определенной фигуры. Вот пример Хизер Бушель:
HTML:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" width="120" height="120" viewBox="0 0 120 120" enable-background="new 0 0 120 120" xml:space="preserve">
  <defs>
    <clipPath id="circle-clip">
      <path class="clip" d="M101.807,123.37c10-0.352,18.193,5.401,18.193,5.401V0H0v128.771c0,0,9.701-5.227,17.069-5.227s10.464,6.314,20.877,6.314
	c10.175,0,12.703-4.209,22.053-4.209s11.981,5.438,20.578,5.438S91.807,123.722,101.807,123.37z">
    <animate id="morph-one" dur="1" begin="0" repeatCount="indefinite"
             attributeName="d" from="M101.807,123.37c10-0.352,18.193,5.401,18.193,5.401V0H0v128.771c0,0,9.701-5.227,17.069-5.227s10.464,6.314,20.877,6.314
	c10.175,0,12.703-4.209,22.053-4.209s11.981,5.438,20.578,5.438S91.807,123.722,101.807,123.37z"
	     to="M101.807,123.37c10-0.352,18.193,5.401,18.193,5.401V0H0v128.771c0,0,9.701-5.227,17.069-5.227s10.464,6.314,20.877,6.314
	c10.175,0,12.703-4.209,22.053-4.209s11.981,5.438,20.578,5.438S91.807,123.722,101.807,123.37z" values="M101.807,123.37c10-0.352,18.193,5.401,18.193,5.401V0H0v128.771c0,0,9.701-5.227,17.069-5.227s10.464,6.314,20.877,6.314
	c10.175,0,12.703-4.209,22.053-4.209s11.981,5.438,20.578,5.438S91.807,123.722,101.807,123.37z;M101.807,135.303c10-0.352,18.193-6.531,18.193-6.531V0H0v128.771c0,0,9.701,4.952,17.069,4.952s10.464-9.299,20.877-9.299
	c10.175,0,12.703,9.299,22.053,9.299s11.981-9.123,20.578-9.123S91.807,135.654,101.807,135.303z
;M101.807,123.37c10-0.352,18.193,5.401,18.193,5.401V0H0v128.771c0,0,9.701-5.227,17.069-5.227s10.464,6.314,20.877,6.314
	c10.175,0,12.703-4.209,22.053-4.209s11.981,5.438,20.578,5.438S91.807,123.722,101.807,123.37z"/>

  </path>
    </clipPath>
  </defs>
  <g clip-path="url(#circle-clip)">
  	<path class="logo" d="M84.995 62.896h-5.788v3.061h5.788c1.037 0 1.68-0.611 1.68-1.512C86.675 63.582 86.032 62.896 84.995 62.896zM86.211 55.411c0-0.828-0.68-1.368-1.43-1.368h-5.574v2.808h5.574C85.531 56.85 86.211 56.274 86.211 55.411zM60 0C26.863 0 0 26.863 0 60c0 33.138 26.863 60 60 60 33.138 0 60-26.862 60-60C120 26.863 93.138 0 60 0zM48.974 72.004h-7.217v-9.178h-8.54v9.178H26V47.996h7.217v8.495h8.54v-8.495h7.217V72.004zM69.094 72.004H52.836V47.996h7.218v17.672h9.04V72.004zM86.962 72.004H71.99V47.996h14.471c4.931 0 7.075 3.312 7.075 6.119 0 2.987-1.752 5.003-4.074 5.507C92.035 60.018 94 62.393 94 65.488 94 68.836 91.749 72.004 86.962 72.004z"/>
  </g>
</svg>

SCSS:

$base: #C8C8A9;
$primary: #FE4365;

body {
  background: $base;
  text-align: center;
}
svg {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -60px;
  margin-left: -60px;
}

.logo {
  fill: $primary;
}

.clip {
  animation: slide 8s infinite;
}

@keyframes slide {
  from {
    transform: translateY(-135px);
  }
  50% {
    transform: translateY(-5px);
  }
  to {
    transform: translateY(-135px);
  }
}

Результат

Анимация вдоль произвольного пути: Элемент <animateMotion>

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

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

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

Атрибут path используется, чтобы указать траекторию движения. Он представляется в том же формате и интерпретируется аналогичным образом, как и атрибут d для элемента path.

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

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

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

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

Код, необходимый, чтобы запустить движение круга по этому пути, следующий:

<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.4c10.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.4c10.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), после чего начинает описывать кривую (с) к другой точке.

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

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

<div class=cont ><svg viewbox="0 0 400 400">
  <path fill="#000">
    <animate 
      attributeName="d" 
      dur="12000ms" 
      repeatCount="indefinite"
      keyTimes="0;0.0388; 0.1176; 0.1564; 0.2352; 0.274; 0.3528; 0.3916; 0.4704; 0.5092; 0.588; 0.6268; 0.7056; 0.7444; 0.8232; 0.862; 0.9408;
                1;"

      values="
        M 68,0 
         52,15.899 0,47 12,212 47,251.793 120,207 110,325.793 78,328.793 57,296.793 10,318.793 
	23.239,408.725 43.143,421.587 173,390 188,353 179,151 177.959,96.168 122,87 111,149 82,146 84,88 177,113 181.843,28 117,0,0  
        Z;M 68,0 
         52,15.899 0,47 12,212 47,251.793 120,207 110,325.793 78,328.793 57,296.793 10,318.793 
	23.239,408.725 43.143,421.587 173,390 188,353 179,151 177.959,96.168 122,87 111,149 82,146 84,88 177,113 181.843,28 117,0,0  
        Z;

              M 78,0 
         15,31.587 0,68.587 25,229.587 7,247.587 6,324.587 66,334.587 77,272.587 106,275.587 104,333.587 
	6.959,307.755 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 68,214.587 72,137.587 107,124.587 107.535,227.573 
	156.882,222.671 164.761,12.862 144.857,0,0  
        Z;M 78,0 
         15,31.587 0,68.587 25,229.587 7,247.587 6,324.587 66,334.587 77,272.587 106,275.587 104,333.587 
	6.959,307.755 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 68,214.587 72,137.587 107,124.587 107.535,227.573 
	156.882,222.671 164.761,12.862 144.857,0,0  
        Z;
             M 78,0 114.167,0 24.667,18.725 0,42.725 0,112.725 14.667,126.725 73.333,109.392 95.333,107.392 
	107.333,126.725 74,260.725 57.333,318.725 49.333,364.726 54,384.726 95.333,402.059 122,398.059 147.333,259.392 163.333,187.392 
	168.934,166.671 174,142.725 185.333,80.059 198,18.725 190.667,4.725 163.333,0;M 78,0 114.167,0 24.667,18.725 0,42.725 0,112.725 14.667,126.725 73.333,109.392 95.333,107.392 
	107.333,126.725 74,260.725 57.333,318.725 49.333,364.726 54,384.726 95.333,402.059 122,398.059 147.333,259.392 163.333,187.392 
	168.934,166.671 174,142.725 185.333,80.059 198,18.725 190.667,4.725 163.333,0 ;

              M 100,5 144.857,0 15,31.587 0,68.587 9,270.587 10.041,325.418 66,334.587 77,272.587 106,275.587 
	104,333.587 11,308.587 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 139,187.587 68,214.587 72,137.587 
	107,124.587 128,156.587 175,134.587 164.761,12.862;M 100,5 144.857,0 15,31.587 0,68.587 9,270.587 10.041,325.418 66,334.587 77,272.587 106,275.587 
	104,333.587 11,308.587 6.157,393.587 71,421.587 136,405.688 188,374.587 176,209.587 139,187.587 68,214.587 72,137.587 
	107,124.587 128,156.587 175,134.587 164.761,12.862;

              M 100,0 28.843,20 0,34 0,202 11.843,279 27.843,292 70.843,277 99.843,280 97.843,338 50.843,337 0,398 
	55.843,438 105.843,431 162.843,378 179.843,294 173.843,214 135.843,187 86.921,195 67.843,161 72.843,125 165.843,109 179.843,31 
	156.843,0;M 100,0 28.843,20 0,34 0,202 11.843,279 27.843,292 70.843,277 99.843,280 97.843,338 50.843,337 0,398 
	55.843,438 105.843,431 162.843,378 179.843,294 173.843,214 135.843,187 86.921,195 67.843,161 72.843,125 165.843,109 179.843,31 
	156.843,0;

              M 100,0 1.99,42.63 9.489,221.121 0,305.117 41.998,323.117 101.994,311.117 103.843,332 107.843,363 
	105.843,394 109.843,406 127.843,406 160.843,399.101 173.843,391 173.843,357 173.843,18 165.843,0 95.843,11 103.843,223 
	86.843,232 73.182,228.376 67.986,155.124 68.997,23.132 47.998,23.132 ;M 100,0 1.99,42.63 9.489,221.121 0,305.117 41.998,323.117 101.994,311.117 103.843,332 107.843,363 
	105.843,394 109.843,406 127.843,406 160.843,399.101 173.843,391 173.843,357 173.843,18 165.843,0 95.843,11 103.843,223 
	86.843,232 73.182,228.376 67.986,155.124 68.997,23.132 47.998,23.132 ;

              M 100,0 3.216,55.855 41.341,131 106.007,117.667 104.674,156.333 47.341,181 26.674,203 35.341,268.333 
	96.007,267 95.341,314.333 78.674,333.667 31.508,328.5 0,391.5 101.007,423 177.507,368 172.507,236 142.507,226.5 136.507,208 
	159.007,197 172.507,181.5 177.507,36.5 152.507,11 93.853,0 ;M 100,0 3.216,55.855 41.341,131 106.007,117.667 104.674,156.333 47.341,181 26.674,203 35.341,268.333 
	96.007,267 95.341,314.333 78.674,333.667 31.508,328.5 0,391.5 101.007,423 177.507,368 172.507,236 142.507,226.5 136.507,208 
	159.007,197 172.507,181.5 177.507,36.5 152.507,11 93.853,0 ;

              M 100,0 44,16.587 1.204,55.672 0,158.587 0,200.587 13.676,209.587 53,199.587 69,139.587 100,139.587 
	94,191.587 39,292.587 0,362.587 34,429.587 108,405.688 163,405.688 175,373.587 153,332.587 115.5,337.587 96,331.587 
	164.51,210.793 164.51,119.587 151,6.587 108,0;M 100,0 44,16.587 1.204,55.672 0,158.587 0,200.587 13.676,209.587 53,199.587 69,139.587 100,139.587 
	94,191.587 39,292.587 0,362.587 34,429.587 108,405.688 163,405.688 175,373.587 153,332.587 115.5,337.587 96,331.587 
	164.51,210.793 164.51,119.587 151,6.587 108,0;

              M 100,0 0,62.189 10.809,86.189 36.809,128.189 62.309,121.189 73.058,122.499 73.058,122.499 
	74.809,136.189 73.319,189.189 70.319,344.188 23.319,359.188 57.319,426.188 131.318,402.29 186.318,402.29 198.318,370.188 
	176.318,329.188 138.818,334.188 132.389,332.21 130.336,308.766 139.318,116.189 145.318,3.189 104.991,0 62.094,35.625 ;M 100,0 0,62.189 10.809,86.189 36.809,128.189 62.309,121.189 73.058,122.499 73.058,122.499 
	74.809,136.189 73.319,189.189 70.319,344.188 23.319,359.188 57.319,426.188 131.318,402.29 186.318,402.29 198.318,370.188 
	176.318,329.188 138.818,334.188 132.389,332.21 130.336,308.766 139.318,116.189 145.318,3.189 104.991,0 62.094,35.625 ;"
            />
  </path>
  </svg> </div>

CSS:

* { box-sizing: border-box; }

.cont {
  height: 100vh;
  padding: 20vmin;
  -webkit-filter: contrast(10);
  background-color: white;
}

svg {
  width: 100%;
  height: 100%;
  opacity: 0.9;
  -webkit-filter: blur(3px);}

Результат

Если бы вы указали начало пути в точке с координатами, отличными от (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.4c10.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.4c10.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>

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

Обратите внимание, что в соответствии со спецификацией:

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


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

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

HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.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.4c10.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.4c10.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"/>
  <circle id="circle" r="20" cx="100" cy="100" fill="tomato" />

  <animateMotion 
           xlink:href="#circle"
           dur="1s"
           begin="click"
           fill="freeze">
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>
<p>Нажмите на круг, чтобы запустить анимацию.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

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

Другой способ заключается в применении преобразования, которое "сбрасывает" координаты круга таким образом, чтобы они устанавливались на ноль перед тем, как будет применен путь.

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

HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
	c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
	c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
	c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
	c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
	C222.5,59,212.5,57.8,202.4,58.3"/>

  <circle id="circle" r="10" cx="0" cy="0" fill="tomato" />

  <animateMotion 
           xlink:href="#circle"
           dur="5s"
           begin="0s"
           fill="freeze"
           repeatCount="indefinite">
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

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

Правила переопределения для animateMotion:

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

Установка ориентации элемента вдоль траектории движения с помощью rotate

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

Иконка автомобиля в следующем примере создана Freepik'ом.

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

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

Значения внутри преобразований на самом деле являются координатами точки, в которой начинает прокладываться первая траектория автомобиля (сразу после команды перемещения М).

Затем автомобиль начинает движение вдоль траектории. Но ... вот, как выглядит это движение:
HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
	c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
	c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
	c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
	c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
	C222.5,59,212.5,57.8,202.4,58.3"/>

  <g id="car" transform="translate(-234.4, -182.8)">
	<path d="M234.4,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C240.8,185.6,238,182.8,234.4,182.8z"/>
	<circle cx="234.4" cy="189.2" r="2.8"/>
	<path d="M263,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C269.4,185.6,266.6,182.8,263,182.8z"/>
	<circle cx="263" cy="189.2" r="2.8"/>
	<path d="M275,171.4c-2.8-0.7-5.2-3-6.3-5.1l-3.9-7.4c-1.1-2.1-3.9-3.8-6.3-3.8h-22.6c-2.4,0-5,1.8-5.7,4.1l-2.4,7
		c-0.2,0.9-1.8,5.5-5,5.5c-2.4,0-5,3.1-5,5.5v8.2c0,2.4,1.9,4.3,4.3,4.3h4.5c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8
		c4.3,0,7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h13.1c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h8.1
		c2.4,0,4.3-1.9,4.3-4.3v-6.5C283.2,172,277.3,172,275,171.4z"/>
	<path d="M241.8,170.3h-12.5c0.7-1.1,1.1-2.2,1.2-2.6l2-5.9c0.6-1.9,2.8-3.5,4.8-3.5h4.5V170.3z"/>
	<path d="M246.1,170.3v-12h10.4c2,0,4.4,1.5,5.3,3.3l3.3,6.3c0.4,0.8,1.1,1.7,2,2.4H246.1z"/>
</g>

  <animateMotion 
           xlink:href="#car"
           dur="3s"
           begin="0s"
           fill="freeze"
            repeatCount="indefinite"
             >
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

Атрибут rotate принимает одно из трех значений:

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

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

HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
	c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
	c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
	c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
	c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
	C222.5,59,212.5,57.8,202.4,58.3"/>

  <g id="car" transform="translate(-234.4, -182.8)">
	<path d="M234.4,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C240.8,185.6,238,182.8,234.4,182.8z"/>
	<circle cx="234.4" cy="189.2" r="2.8"/>
	<path d="M263,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C269.4,185.6,266.6,182.8,263,182.8z"/>
	<circle cx="263" cy="189.2" r="2.8"/>
	<path d="M275,171.4c-2.8-0.7-5.2-3-6.3-5.1l-3.9-7.4c-1.1-2.1-3.9-3.8-6.3-3.8h-22.6c-2.4,0-5,1.8-5.7,4.1l-2.4,7
		c-0.2,0.9-1.8,5.5-5,5.5c-2.4,0-5,3.1-5,5.5v8.2c0,2.4,1.9,4.3,4.3,4.3h4.5c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8
		c4.3,0,7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h13.1c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h8.1
		c2.4,0,4.3-1.9,4.3-4.3v-6.5C283.2,172,277.3,172,275,171.4z"/>
	<path d="M241.8,170.3h-12.5c0.7-1.1,1.1-2.2,1.2-2.6l2-5.9c0.6-1.9,2.8-3.5,4.8-3.5h4.5V170.3z"/>
	<path d="M246.1,170.3v-12h10.4c2,0,4.4,1.5,5.3,3.3l3.3,6.3c0.4,0.8,1.1,1.7,2,2.4H246.1z"/>
</g>

  <animateMotion 
           xlink:href="#car"
           dur="3s"
           begin="0s"
           fill="freeze"
            repeatCount="indefinite"
                 rotate="auto"
             >
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

Если вы хотите, чтобы автомобиль перемещался с внешней стороны области, очерченной траекторией, это делается с помощью значения auto-reverse.

HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
	c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
	c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
	c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
	c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
	C222.5,59,212.5,57.8,202.4,58.3"/>

  <g id="car" transform="translate(-234.4, -182.8)">
	<path d="M234.4,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C240.8,185.6,238,182.8,234.4,182.8z"/>
	<circle cx="234.4" cy="189.2" r="2.8"/>
	<path d="M263,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C269.4,185.6,266.6,182.8,263,182.8z"/>
	<circle cx="263" cy="189.2" r="2.8"/>
	<path d="M275,171.4c-2.8-0.7-5.2-3-6.3-5.1l-3.9-7.4c-1.1-2.1-3.9-3.8-6.3-3.8h-22.6c-2.4,0-5,1.8-5.7,4.1l-2.4,7
		c-0.2,0.9-1.8,5.5-5,5.5c-2.4,0-5,3.1-5,5.5v8.2c0,2.4,1.9,4.3,4.3,4.3h4.5c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8
		c4.3,0,7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h13.1c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h8.1
		c2.4,0,4.3-1.9,4.3-4.3v-6.5C283.2,172,277.3,172,275,171.4z"/>
	<path d="M241.8,170.3h-12.5c0.7-1.1,1.1-2.2,1.2-2.6l2-5.9c0.6-1.9,2.8-3.5,4.8-3.5h4.5V170.3z"/>
	<path d="M246.1,170.3v-12h10.4c2,0,4.4,1.5,5.3,3.3l3.3,6.3c0.4,0.8,1.1,1.7,2,2.4H246.1z"/>
</g>

  <animateMotion 
           xlink:href="#car"
           dur="3s"
           begin="0s"
           fill="freeze"
            repeatCount="indefinite"
                 rotate="auto-reverse"
             >
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>
<p>Click on the circle to animate it.</p>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

Так что, если мы применим преобразование к g с ID car, автомобиль будет перемещаться так, как нам нужно. Преобразование масштаба должно только чередоваться с преобразованием, которое мы применяли ранее.

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

И окончательная демо-версия будет выглядеть следующим образом:
HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
	c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
	c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
	c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
	c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
	C222.5,59,212.5,57.8,202.4,58.3"/>

  <g id="car" transform="scale (-1, 1) translate(-234.4, -182.8)">
	<path d="M234.4,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C240.8,185.6,238,182.8,234.4,182.8z"/>
	<circle cx="234.4" cy="189.2" r="2.8"/>
	<path d="M263,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C269.4,185.6,266.6,182.8,263,182.8z"/>
	<circle cx="263" cy="189.2" r="2.8"/>
	<path d="M275,171.4c-2.8-0.7-5.2-3-6.3-5.1l-3.9-7.4c-1.1-2.1-3.9-3.8-6.3-3.8h-22.6c-2.4,0-5,1.8-5.7,4.1l-2.4,7
		c-0.2,0.9-1.8,5.5-5,5.5c-2.4,0-5,3.1-5,5.5v8.2c0,2.4,1.9,4.3,4.3,4.3h4.5c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8
		c4.3,0,7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h13.1c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h8.1
		c2.4,0,4.3-1.9,4.3-4.3v-6.5C283.2,172,277.3,172,275,171.4z"/>
	<path d="M241.8,170.3h-12.5c0.7-1.1,1.1-2.2,1.2-2.6l2-5.9c0.6-1.9,2.8-3.5,4.8-3.5h4.5V170.3z"/>
	<path d="M246.1,170.3v-12h10.4c2,0,4.4,1.5,5.3,3.3l3.3,6.3c0.4,0.8,1.1,1.7,2,2.4H246.1z"/>
</g>

  <animateMotion 
           xlink:href="#car"
           dur="3s"
           begin="0s"
           fill="freeze"
           repeatCount="indefinite"
           rotate="auto-reverse"
             >
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
p {
  color: #aaa;
  text-align: center;
  margin: 2em 0;
}

Результат

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

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

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

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

Атрибут keyPoints на момент написания данной статьи не поддерживался в Chrome и Firefox. Он пока еще не реализован, как и другие части спецификации, о которых я упоминала ранее.

Перемещение текста вдоль произвольного пути

Перемещение текста вдоль произвольного пути отличается от перемещения других элементов SVG вдоль пути. Чтобы анимировать текст, вам нужно будет использовать элемент <animate>, а не <animateMotion>.

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

Текст, который будет располагаться вдоль пути, будет определяться внутри элемента <textPath>, а не как дочерний элемент от <text>.

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

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="myPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.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.4c10.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.4c10.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"/>
  <text>
    <textpath xlink:href="#myPath">
      Текст, уложенный вдоль пути.
    </textpath>
  </text>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
text {
  fill: deepPink;
  font-size: 2em;
}

Результат

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

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

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

HTML:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="myPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.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.4c10.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.4c10.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"/>
  <text>
    <textpath xlink:href="#myPath">
      Text laid out along a path.

      <animate attributeName="startOffset" from="0%" to ="100%" begin="0s" dur="5s" repeatCount="indefinite" keyTimes="0;1" calcMode="spline" keySplines="0.1 0.2 .22 1"/>
    </textpath>
  </text>
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}
text {
  fill: deepPink;
  font-size: 2em;
}

Результат

Анимация преобразований: Элемент <animateTransform>

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

Атрибут type используется для указания типа преобразования, которое мы анимируем. Он принимает одно из пяти значений: translate, scale, rotate, skewX и skewY.

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

  • + Для type="translate", каждое отдельное значение выражается как <txt> [,<Ty>];
  • + Для type="scale", каждое отдельное значение выражается как <sx> [,<sy>];
  • + Для type="rotate", каждое отдельное значение выражается как [<cx> <cy>];
  • + Для type="skewX" and type="skewY", каждое отдельное значение выражается как <skew-angle>.

Возвращаемся к предыдущей демонстрации, где мы вращали розовый прямоугольник, используя элемент <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. Демонстрация для приведенного выше кода выглядит следующим образом:
HTML:

<svg width="500" height="150">
  <style>
    rect {
      -moz-transform-origin: 75px 75px;
      transform-origin: 50% 50%;
    }
  </style>
  <rect id="deepPink-rectangle" width="50" height="50" x="50" y="50" fill="deepPink"  transform="rotate(0) translate(0 0)"/>

  <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"

           />
</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

HTML:

<svg viewBox="0 0 160 160" width="160" height="160">
  <circle cx="80" cy="80" r="50" />
  <g transform=" matrix(0.866, -0.5, 0.25, 0.433, 80, 80)">
    <path d="M 0,70 A 65,70 0 0,0 65,0 5,5 0 0,1 75,0 75,70 0 0,1 0,70Z" fill="#FFF">
      <animateTransform attributeName="transform" type="rotate" from="360 0 0" to="0 0 0" dur="1s" repeatCount="indefinite" />
    </path>
  </g>
  <path d="M 50,0 A 50,50 0 0,0 -50,0Z" transform="matrix(0.866, -0.5, 0.5, 0.866, 80, 80)" />
</svg>

CSS:

* {
  margin: 0;
  padding: 0;
}

html, body {
  height: 100%;
}

body {
  background: #FC0;
}

svg {
  position: fixed;
  top: 20%;
  height: 60%;
  left: 20%;
  width: 60%;
}

Результат

Анимировать одно преобразование довольно просто, однако все может стать действительно сложно и запутано, когда у нас включены несколько преобразований. Особенно потому, что один элемент 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 секунд, а затем возвращается к первоначальному значению. Каждый раз, когда вы нажимаете на прямоугольник, запускается анимация, и цвет изменяется на 3 секунды.

HTML:

<svg width="500" height="350">
  <style>
    rect {
      -moz-transform-origin: 75px 75px;
      transform-origin: 50% 50%;
    }
  </style>
  <rect id="deepPink-rectangle" width="50" height="50" x="225" y="125" fill="deepPink"  transform="rotate(0) translate(0 0)"/>

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

           />
  <set xlink:href="#deepPink-rectangle" attributeName="fill"
       to="#0099AA" begin="click" dur="3s" />

</svg>

CSS:

svg {
  border: 3px solid #eee;
  display: block;
  margin: 1em auto;
}

Результат

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

Не все атрибуты SVG могут быть анимированы, и не все атрибуты, которые могут быть анимированы, могут быть анимированы с использованием всех элементов анимации.

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

Заключение

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

Перед вами безграничный океан возможностей. Не бойтесь сделать что-нибудь сумасшедшее! И не забудьте поделиться тем, что вы делаете, с сообществом; мы хотели бы увидеть, что у вас получилось. Спасибо за ваше внимание.

Сергей Бензенкоавтор-переводчик статьи «A Guide to SVG Animations (SMIL)»