
✨ Анимация линий и замкнутых контуров 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 } 6 svg 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 } 11 svg 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 } 9 svg 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 } 11 svg 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 } 11 svg 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 } 11 svg 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 } 9 svg 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 } 11 svg 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 } 11 svg 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 } 11 svg .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(). Правильно подобранная функция плавности меняет восприятие анимации сильнее, чем сдвиг стартовой точки на пару пикселей.



