32 Модуль 7 Графика и эффекты 16 мин урок 7 из 25

Анимация SVG

Линия, которая рисуется сама; галочка, которая появляется штрихом; подчёркивание, растущее из-под курсора. Всё двумя свойствами обводки.

Что ты научишься делать

  • Поймёшь приём с dasharray и dashoffset — основу всех «рисующихся» линий
  • Сделаешь анимированную галочку успеха и подчёркивание при наведении
  • Выберешь между CSS, SMIL и скриптом осознанно

#Фокус, который объясняется за минуту

Эффект рисующейся линии выглядит сложным, а держится на двух свойствах пунктира.

stroke-dasharray задаёт длину штриха и промежутка. Если сделать штрих длиной во весь контур, пунктир превращается в сплошную линию — визуально ничего не меняется.

stroke-dashoffset сдвигает этот пунктир вдоль контура. Сдвинули на всю длину — линия ушла за пределы контура и исчезла. Анимируем сдвиг обратно к нулю — линия «рисуется».

Интерактивный разбор доступен при включённом JavaScript.

#Откуда взять длину контура

Нужно точное число — длина линии в единицах viewBox. На глаз её не угадать: у кривой длина заметно больше, чем расстояние между концами. Есть три способа получить её честно.

СпособКакКогда
Спросить браузерpath.getTotalLength() в консолиОдин раз при вёрстке — самый точный
Взять с запасомПоставить заведомо большее числоБыстро, но старт анимации будет с задержкой
Задать самомуАтрибут pathLength="100"Лучший вариант: длина становится ровно 100
Галочка успеха
В песочницу

#Подчёркивание, растущее при наведении

Тот же приём, но без SVG вовсе: линию рисует псевдоэлемент, а анимируется transform. Это дешевле для браузера, чем анимация ширины.

Подчёркивание из-под курсора
В песочницу

#Движение по траектории

Второй приём, который выглядит дорого, а стоит трёх строк: элемент едет вдоль произвольной кривой. Раньше это считали скриптом покадрово, теперь есть offset-path.

Путь задаётся той же строкой, что и в атрибуте d, а offset-distance говорит, насколько далеко по нему уехал элемент. Анимируется как раз расстояние.

CSSplane.css
.plane {  offset-path: path("M0 60 C 60 0, 140 120, 220 20");  offset-distance: 0%;  /* Разворачивает элемент по касательной к пути */  offset-rotate: auto;  animation: fly 3s ease-in-out infinite alternate;}@keyframes fly {  to { offset-distance: 100%; }}

#CSS, SMIL или скрипт

У SVG есть собственный язык анимации — <animate>, он же SMIL. Плюс работают обычные CSS-анимации и, конечно, JavaScript. Выбор не про вкус.

CSSSMILJavaScript
Что умеетВсё, что можно задать свойством: обводку, transform, opacity, цветЕщё и форму пути, движение по траекторииВсё, включая реакцию на данные
Управление из кодаЧерез классы и переменныеНеудобноеПолное
Уважает reduced-motionДа, через медиазапросНет — надо отключать вручнуюНадо проверять самому
Когда братьВ девяти случаях из десятиМорфинг формы, движение по путиКогда анимация зависит от данных или ввода

#Как это выглядит в реальном проекте

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

CSSspinner.css
@layer components {  .spinner {    inline-size: 1.25em;    block-size: 1.25em;    fill: none;    stroke: currentColor;    stroke-width: 3;    stroke-linecap: round;  }  .spinner circle {    /* Дуга в четверть окружности, которая едет по кругу */    stroke-dasharray: 25 75;    transform-origin: center;    animation: spin 1s linear infinite;  }  @keyframes spin {    to { transform: rotate(360deg); }  }  /* Без движения — просто статичное кольцо, а не пустота */  @media (prefers-reduced-motion: reduce) {    .spinner circle {      animation: none;      stroke-dasharray: none;      opacity: .45;    }  }}

#Анимация по скроллу

Линию часто хотят рисовать по мере прокрутки. Самый надёжный способ сегодня — IntersectionObserver: блок появился в поле зрения, добавили класс, анимация пошла один раз.

Есть и чисто декларативный путь — animation-timeline: view(), привязка анимации к прокрутке без единой строки JavaScript. Он работает в браузерах на движке Chromium, но в остальных пока нет, поэтому его дают сверх рабочего варианта, а не вместо.

CSSПрогрессивное улучшение
/* База: класс добавляет скрипт, работает везде */.line.is-visible { animation: draw .8s ease forwards; }/* Улучшение там, где поддерживается */@supports (animation-timeline: view()) {  .line {    animation: draw linear both;    animation-timeline: view();    animation-range: entry 20% cover 60%;  }}

#Когда НЕ надо

  • Анимировать d без крайней нужды. Морфинг формы — самая дорогая анимация из возможных: браузер пересчитывает геометрию на каждом кадре. Для двух-трёх состояний дешевле поменять весь SVG.
  • Бесконечная анимация вне экрана. Спиннер, который крутится в скрытом блоке, продолжает есть кадры. Останавливай через IntersectionObserver или убирай элемент.
  • Анимация ради заполнения паузы. Если загрузка занимает 200 мс, спиннер успеет только мигнуть — это раздражает сильнее, чем ожидание. Показывай индикатор с задержкой в 300–400 мс.
  • Забыть про prefers-reduced-motion. Для части людей движение на экране вызывает физическое недомогание. Это не настройка вкуса.

#Поддержка браузерами

ВозможностьСостояние
Анимация stroke-dashoffset через CSSВезде и давно
pathLengthВо всех современных браузерах
SMIL (<animate>)Работает везде, но развивается слабо
animation-timeline: view()Пока только в браузерах на Chromium — давать сверх рабочего варианта
offset-path (движение по траектории)Во всех современных браузерах

Где спотыкаются почти все

Длина контура подобрана на глаз

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

Как надо Ставь pathLength="100" и работай в процентах.

Анимируется fill или ширина вместо transform

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

Как надо По возможности анимируй transform и opacity. Обводка — исключение: она дешёвая.

Забыт transform-origin у вращения в SVG

Почему По умолчанию точка отсчёта — левый верхний угол системы координат, и фигура улетает по дуге вместо вращения на месте.

Как надо Пиши transform-origin: center и transform-box: fill-box.

Нет ветки prefers-reduced-motion

Почему Человеку с вестибулярными нарушениями движущаяся графика может испортить самочувствие.

Как надо Добавь медиазапрос: анимацию отключи, а конечное состояние оставь видимым.

Пунктир обводки stroke-dasharray
Длины штриха и промежутка. Штрих во всю длину контура выглядит сплошной линией.
Сдвиг пунктира stroke-dashoffset
Смещение пунктира вдоль контура. Анимация этого свойства даёт эффект рисования.
Заданная длина pathLength
Атрибут, переопределяющий длину контура для расчётов обводки.
SMIL SMIL
Собственный язык анимации SVG через тег animate.

Проверь себя

  1. Почему при <code>stroke-dasharray: 100; stroke-dashoffset: 100</code> линия исчезает?

    Штрих длиной во весь контур сдвинулся ровно на эту длину — видимой части не осталось.

  2. Что даёт атрибут <code>pathLength="100"</code>?

    Реальная геометрия не меняется, меняется система отсчёта для обводки. Считать длину больше не нужно.

  3. Когда SMIL уместнее CSS?

    CSS покрывает почти всё. У SMIL остаются две вещи, которых в CSS нет: анимация самого <code>d</code> и движение по пути.

  4. Фигура при вращении улетает по дуге вместо вращения на месте. Что забыто?

    По умолчанию точка отсчёта в SVG — начало системы координат, а не центр фигуры.

  5. Что обязательно добавить к любой заметной анимации?

    Для части людей движение вызывает физическое недомогание. Анимацию отключают, а конечное состояние оставляют видимым.

Задание

Оживить галочку

Галочка нарисована, но появляется сразу. Сделай так, чтобы контур рисовался: задай обоим элементам pathLength="100", спрячь их через stroke-dasharray и stroke-dashoffset, опиши @keyframes и запусти анимацию — галочку с задержкой после кольца.

Обязательно добавь ветку prefers-reduced-motion: без движения галочка должна быть просто видна.

Открыть в песочнице
    Подсказка 1

    Атрибут пишется прямо в разметке: <circle class="ring" ... pathLength="100"/>

    Подсказка 2

    Прячем линию: .ring { stroke-dasharray: 100; stroke-dashoffset: 100; }

    Подсказка 3

    Кадры и запуск: @keyframes draw { to { stroke-dashoffset: 0; } } и animation: draw .5s ease forwards;

    Подсказка 4

    Задержка — третье значение в сокращении: animation: draw .35s ease .45s forwards;

    Показать решение
    HTMLindex.html
    <svg viewBox="0 0 52 52" width="90" height="90" role="img" aria-label="Готово">  <circle class="ring" cx="26" cy="26" r="23" pathLength="100"/>  <path class="tick" d="M15 27l8 8 15-16" pathLength="100"/></svg>
    CSSstyle.css
    svg { fill: none; stroke-linecap: round; stroke-linejoin: round; }.ring {  stroke: #0f8f6a;  stroke-width: 3;  stroke-dasharray: 100;  stroke-dashoffset: 100;  animation: draw .5s ease forwards;}.tick {  stroke: #0f8f6a;  stroke-width: 4;  stroke-dasharray: 100;  stroke-dashoffset: 100;  animation: draw .35s ease .45s forwards;}@keyframes draw {  to { stroke-dashoffset: 0; }}@media (prefers-reduced-motion: reduce) {  .ring, .tick {    animation: none;    stroke-dashoffset: 0;  }}

    Проверь себя