Skip to content

Всё для WordPress, веб-разработки — и не только

✨ Анимация линий и замкнутых контуров SVG на чистом CSS

✨ Анимация линий и замкнутых контуров SVG на чистом CSS

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

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

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

💡 Быстрый обзор:

  • Узнайте длину SVG-пути через getTotalLength() или Illustrator и пропишите число в CSS.
  • Настройте stroke-dasharray: LENGTH и анимируйте stroke-dashoffset от LENGTH до 0.
  • Для реверса направления меняйте местами штрих и пробел в stroke-dasharray и толкайте stroke-dashoffset вперёд.
  • Сместите начальную точку добавкой к stroke-dashoffset половины (или произвольной доли) длины.
  • Запустите анимацию «изнутри наружу»: старт от HALF_LENGTH, финиш на FULL_LENGTH.
  • Настройте двойные линии через половинный stroke-dasharray и четверть-сдвиг stroke-dashoffset.

Техническая база: штрихи, пробелы и длина пути

В основе любой SVG-анимации лежат два CSS-свойства: stroke-dasharray и stroke-dashoffset. Первое задаёт узор «штрих-пробел» вдоль контура, второе сдвигает этот узор. Если сделать штрих равным полной длине пути, а затем анимировать сдвиг, линия «нарисуется» на глазах.

Ключевой шаг перед любой анимацией, узнать полную длину пути. Способы:

  • JavaScript: document.querySelector('path').getTotalLength(), один вызов в консоли, число подставляется в CSS жёстко. Никакого рантайма.
  • Illustrator: панель «Информация о документе» показывает длину выделенного пути.
  • Свой скрипт: если путей десятки или сотни, имеет смысл написать автоматический обход и разметку длин.

Важный нюанс: getTotalLength() и Illustrator могут дать немного разные цифры для одного и того же пути. Причина, разная точность вычислений. Расхождение обычно в пределах 1-2 единиц, на визуал не влияет.

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

Базовая анимация: рисуем линию от начала к концу

Простейший случай: открытый путь длиной, скажем, 100 единиц. Ставим штрих на всю длину и анимируем смещение от 100 до 0:

1@keyframes reveal {
2 to {
3 stroke-dashoffset: 0;
4 }
5}
6svg path {
7 stroke-dashoffset: 100;
8 stroke-dasharray: 100;
9 animation: reveal 1s ease-in-out 0s forwards;
10}

Что здесь происходит: stroke-dasharray: 100 задаёт 100 единиц сплошной линии и 100 единиц пустоты (второе значение по умолчанию равно первому). stroke-dashoffset: 100 сдвигает узор так, что видимая часть уходит за пределы пути, линия невидима. Анимация сводит смещение к нулю, и штрих вползает на место.

Несколько практических замечаний:

  • FULL_LENGTH замените на реальное число без единиц измерения. CSS-движок не понимает px или em внутри stroke-dasharray.
  • Селектор svg path, для одного контура. Если в SVG несколько путей, используйте классы, ID или псевдоклассы вроде path:nth-of-type(3). Инструменты для отладки, :nth Tester и nthmaster.com.
  • Имя @keyframes должно быть уникальным для каждого варианта анимации. Разные пути часто требуют собственных ключевых кадров.
  • Избегайте отрицательных значений stroke-dashoffset: Safari при отрицательном смещении может моргать или показывать артефакты.

Управление направлением: реверс прорисовки

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

Решение, инвертировать узор штриха: линия стартует невидимой (0 штриха, 100 пробела) и становится видимой (100 штриха, 0 пробела), а смещение тянет её назад:

1@keyframes reversed {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 0;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 100;
9 }
10}
11svg path {
12 animation: reversed 1s ease-in-out 0s forwards;
13}

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

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

1@keyframes alternative-reversed {
2 from {
3 stroke-dashoffset: 100;
4 }
5 to {
6 stroke-dashoffset: 200;
7 }
8}
9svg path {
10 stroke-dasharray: 100;
11 animation: alternative-reversed 1s ease-in-out 0s forwards;
12}

Здесь stroke-dasharray зафиксирован на полной длине, а смещение уходит от 100 к 200. Результат тот же, линия рисуется в обратном направлении. Используйте вариант, который вам интуитивно понятнее.

Смещение начальной точки

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

Старт с противоположной стороны

Для симметричных контуров достаточно смещения на половину длины:

1@keyframes start-from-other-side {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 50;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 50;
9 }
10}
11svg path {
12 animation: start-from-other-side 1s ease-in-out 0s forwards;
13}

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

Реверс направления и произвольный старт

Чтобы одновременно развернуть направление и сместить стартовую точку, добавьте полную длину к конечному смещению:

1@keyframes other-side-and-reverse {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 50;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 150;
9 }
10}
11svg path {
12 animation: other-side-and-reverse 1s ease-in-out 0s forwards;
13}

Для совсем произвольной точки, подставьте любое значение вместо 50. Методом подбора (плюс-минус 5-10 единиц) находится оптимальная позиция. В одном из проектов стартовая точка на иконке бита Twitch была сдвинута на 17 пикселей, число некрасивое, но визуально идеальное.

Анимация «изнутри наружу»

Линия растёт от середины в обе стороны одновременно, классический приём для логотипов и инфографики:

1@keyframes inside-out {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 50;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 100;
9 }
10}
11svg path {
12 animation: inside-out 1s ease-in-out 0s forwards;
13}

Смещение стартует от половины длины (центр пути) и уходит к полной длине, вытягивая штрих за собой. Для несимметричных форм точку старта можно сместить дополнительно:

1@keyframes custom-inside-out {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 84;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 134;
9 }
10}

Здесь 84 = HALF_LENGTH + 34. Константа 34 подобрана вручную для конкретной формы. Универсального рецепта нет, пробуйте, смотрите, корректируйте.

Анимация «снаружи внутрь»: двойное перо

Противоположный подход: линия рисуется с двух концов одновременно, сходясь к середине. На открытых контурах это даёт эффект «двух перьев»:

1@keyframes outside-in {
2 from {
3 stroke-dasharray: 0 100;
4 }
5 to {
6 stroke-dasharray: 50 0;
7 }
8}
9svg path {
10 animation: outside-in 1s ease-in-out 0s forwards;
11}

Штрих доходит только до половины длины, разрыв схлопывается вдвое быстрее, чем в базовой анимации. stroke-dashoffset здесь не нужен: узор «0 пробела» в конечном состоянии сам по себе заставляет обе половинки штриха сомкнуться.

Полный контроль: начальная и конечная точки

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

1@keyframes custom-start-end {
2 from {
3 stroke-dasharray: 0 100;
4 stroke-dashoffset: 30;
5 }
6 to {
7 stroke-dasharray: 100 0;
8 stroke-dashoffset: 80;
9 }
10}
11svg path {
12 animation: custom-start-end 1s ease-in-out 0s forwards;
13}

Значения 30 и 80, произвольные; from-смещение меньше to-смещения, единственное жёсткое правило. Если точки близки, один сегмент летит быстро, другой ползёт медленно, это нормально и даже красиво.

Не бойтесь выходить за пределы полной длины в to-значении. stroke-dashoffset: 130 при длине 100, легально, это просто сдвигает цикл штрихов на второй круг. Главное, избегать отрицательных значений.

Двойные линии и спецэффекты

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

1@keyframes double-lines {
2 from {
3 stroke-dasharray: 0 50;
4 stroke-dashoffset: 25;
5 }
6 to {
7 stroke-dasharray: 50 0;
8 stroke-dashoffset: 50;
9 }
10}
11svg path {
12 animation: double-lines 1s ease-in-out 0s forwards;
13}

Для сложных форм массив штрихов может состоять из нескольких компонентов. Сумма всех значений массива должна равняться полной длине. Реальный пример для знака бесконечности:

1@keyframes infinity-double-lines {
2 from {
3 stroke-dasharray: 0 53 0 47;
4 stroke-dashoffset: 37;
5 }
6 to {
7 stroke-dasharray: 53 0 47 0;
8 stroke-dashoffset: 60;
9 }
10}
11svg .infinity {
12 animation: infinity-double-lines 8s ease-in-out 0s alternate infinite;
13 stroke: #8D69D8;
14}

Четыре компонента массива (53 + 47 = 100 = полная длина) меняются местами между from и to. Анимация зациклена (infinite) и реверсируется (alternate), получается медитативный дышащий контур, который никогда не повторяется одинаково.

Видео: SVG-анимация на практике

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

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

⁉️🤔 Частые вопросы

Зачем знать длину пути заранее, если есть getTotalLength()?

getTotalLength(), это JavaScript, а наша цель, чистый CSS без рантайм-зависимостей. Вы измеряете длину ОДИН раз (в консоли или Illustrator), жёстко прописываете число в CSS, и анимация работает без единой строчки JS.

Можно ли анимировать несколько путей одной анимацией?

Да, если у них одинаковая длина и желаемое поведение. На практике пути редко совпадают, проще задать каждому свой @keyframes или использовать CSS-переменные с разными значениями длины.

Почему Safari мигает при отрицательном stroke-dashoffset?

Это известный баг рендеринга WebKit: отрицательные значения смещения иногда интерпретируются как мгновенный сброс в 0. Лечение, всегда держать stroke-dashoffset ≥ 0, при необходимости сдвига добавляйте полную длину вместо ухода в минус.

Что делать, если анимация «дёргается» в начале или конце?

Добавьте forwards в animation (сохраняет конечное состояние) и уберите alternate, если не нужен реверс. Также проверьте, нет ли конфликта с другими CSS-правилами для того же элемента, например, transition на том же свойстве.

Работает ли это с stroke-dasharray на замкнутых контурах со сложной геометрией?

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

Стоит ли писать анимацию вручную, когда есть библиотеки?

Короткий ответ: зависит от масштаба. Для разовой иконки на лендинге ручное управление даёт идеальный результат без лишних зависимостей. Пятнадцать минут на подбор смещения, и контур рисуется именно так, как вы задумали, а не как решил разработчик библиотеки.

Когда мы делали SVG-разделители для Elementor, ручное управление окупилось трёхкратно. Плагин использует десятки анимированных разделителей, и каждый, с индивидуально настроенными штрихами. Ни одна библиотека не дала бы такого контроля над эстетикой.

Поверх техник из этой статьи накручиваются:

  • Управление состояниями анимации через JavaScript (старт/стоп/рестарт);
  • Ленивый запуск через Intersection Observer, контур рисуется, только когда попадает во вьюпорт;
  • Цепочки анимаций через animation-delay;
  • Кастомные функции плавности (cubic-bezier());
  • Дополнительная работа с прозрачностью в @keyframes;
  • Эластичная анимация пунктирных линий («муравьиная дорожка»).

Берите за основу чертежи из статьи, подставляйте свои значения длин и экспериментируйте с cubic-bezier(). Правильно подобранная функция плавности меняет восприятие анимации сильнее, чем сдвиг стартовой точки на пару пикселей.