DrawSVG эффекты

Эффект «рисования» SVG основан на управлении параметрами обводки пути. Векторная линия в SVG имеет вычисляемую длину, которая используется для создания иллюзии постепенного появления контура.

Ключевые свойства SVG:

  • stroke-dasharray — задаёт шаблон штриховки линии
  • stroke-dashoffset — смещает начало отображаемого штриха вдоль пути
  • pathLength — нормализованная длина пути (необязательный параметр, но полезный для упрощения расчётов)

Базовый принцип DrawSVG заключается в следующем: вся длина линии скрывается с помощью stroke-dashoffset, равного полной длине пути, после чего значение анимируется до нуля.

Подготовка SVG для анимации

Перед применением анимации SVG-путь должен быть корректно подготовлен. Основное требование — наличие атрибута stroke и отсутствие заливки.

<svg viewBox="0 0 400 200">
  <path
    id="line"
    d="M10 100 C 100 10, 300 10, 390 100"
    fill="none"
    stroke="black"
    stroke-width="2"
  />
</svg>

Дальнейшая работа строится вокруг вычисления длины пути:

const path = document.querySelector("#line");
const length = path.getTotalLength();

Этот метод возвращает реальную длину кривой, включая Bézier сегменты.

Базовая реализация DrawSVG через Motion One

Библиотека Motion One предоставляет универсальный API animate, который позволяет изменять любые числовые свойства DOM и SVG.

Простейшая реализация эффекта рисования строится на анимации strokeDashoffset.

import { animate } from "motion";

const path = document.querySelector("path");
const length = path.getTotalLength();

path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;

animate(
  path,
  { strokeDashoffset: 0 },
  { duration: 2, easing: "ease-in-out" }
);

Логика работы:

  • полная длина линии скрывается через strokeDashoffset
  • анимация уменьшает смещение до 0
  • создаётся эффект последовательного проявления контура

Синхронизация dasharray и dashoffset

Корректный DrawSVG эффект требует строгой синхронизации двух параметров:

  • stroke-dasharray = длина пути
  • stroke-dashoffset = длина пути → 0

Расширенный вариант с динамическим вычислением:

import { animate } from "motion";

function drawSVG(path) {
  const length = path.getTotalLength();

  path.style.strokeDasharray = `${length}`;
  path.style.strokeDashoffset = `${length}`;

  return animate(
    path,
    { strokeDashoffset: 0 },
    { duration: 1.5, easing: "ease-out" }
  );
}

document.querySelectorAll("path").forEach(drawSVG);

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

Управление скоростью отрисовки через длину пути

SVG-пути могут значительно различаться по длине, что приводит к несогласованной визуальной скорости анимации. Решение — нормализация длительности относительно длины.

import { animate } from "motion";

function drawSVG(path) {
  const length = path.getTotalLength();

  path.style.strokeDasharray = length;
  path.style.strokeDashoffset = length;

  const duration = Math.min(3, Math.max(0.8, length / 300));

  return animate(
    path,
    { strokeDashoffset: 0 },
    { duration, easing: "linear" }
  );
}

Подход обеспечивает:

  • короткие линии не анимируются слишком быстро
  • длинные контуры не растягивают тайминг чрезмерно

Анимация нескольких путей как единого рисунка

SVG-иллюстрации часто состоят из множества <path> элементов. Для создания эффекта последовательного рисования применяется каскадная анимация.

import { animate } from "motion";

const paths = document.querySelectorAll("svg path");

paths.forEach((path, index) => {
  const length = path.getTotalLength();

  path.style.strokeDasharray = length;
  path.style.strokeDashoffset = length;

  animate(
    path,
    { strokeDashoffset: 0 },
    {
      duration: 1.2,
      delay: index * 0.15,
      easing: "ease-out"
    }
  );
});

Ключевая идея — использование delay, формирующего временную шкалу без отдельного timeline-объекта.

Использование Motion One timeline для DrawSVG

Motion One поддерживает композицию анимаций через timeline, что позволяет точно управлять последовательностью отрисовки.

import { timeline } from "motion";

const paths = document.querySelectorAll("path");

const sequence = paths.map((path) => {
  const length = path.getTotalLength();

  path.style.strokeDasharray = length;
  path.style.strokeDashoffset = length;

  return [
    path,
    { strokeDashoffset: 0 },
    { duration: 1 }
  ];
});

timeline(sequence);

Преимущество такого подхода:

  • декларативная структура
  • отсутствие ручного расчёта delay
  • единая система синхронизации

DrawSVG в связке с easing-кривыми

Характер линии сильно зависит от функции сглаживания. Для DrawSVG наиболее применимы:

  • ease-out — быстрый старт, плавное завершение
  • ease-in-out — равномерное распределение скорости
  • spring-анимации для органического эффекта

Пример spring-модели:

import { animate } from "motion";

animate(path, {
  strokeDashoffset: 0
}, {
  type: "spring",
  stiffness: 120,
  damping: 20
});

Spring-подход создаёт «живое» поведение линии, особенно заметное на кривых.

Привязка DrawSVG к scroll-событиям

SVG-отрисовка часто используется как скролл-индикатор или часть интерактивных иллюстраций. В Motion One используется scroll или inView механика.

import { animate, inView } from "motion";

const path = document.querySelector("path");
const length = path.getTotalLength();

path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;

inView(path, () => {
  animate(path, { strokeDashoffset: 0 }, { duration: 1.5 });
});

Для сложных сцен применяется контроль прогресса:

import { animate, scroll } from "motion";

const path = document.querySelector("path");
const length = path.getTotalLength();

path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;

scroll(
  animate(path, { strokeDashoffset: 0 }, { duration: 1 }),
  { target: path }
);

Такой подход связывает прогресс анимации с положением элемента в viewport.

Масштабирование DrawSVG для больших сцен

При работе с большим количеством SVG-элементов возникает проблема производительности. Основные оптимизации:

  • кэширование длины пути
  • предварительная установка stroke параметров через CSS
  • группировка анимаций по DOM-узлам
  • минимизация одновременных активных анимаций

Пример предварительной подготовки:

const paths = document.querySelectorAll("path");

paths.forEach((path) => {
  const length = path.getTotalLength();

  path.dataset.length = length;

  path.style.strokeDasharray = length;
  path.style.strokeDashoffset = length;
});

Далее анимация запускается без пересчёта геометрии.

Комбинирование DrawSVG с opacity и transform

Эффект рисования усиливается дополнительными трансформациями:

  • плавное появление (opacity)
  • масштабирование (scale)
  • лёгкий сдвиг вдоль оси (translate)
import { animate } from "motion";

animate(
  path,
  {
    strokeDashoffset: 0,
    opacity: 1,
    transform: "translateY(0px)"
  },
  {
    duration: 1.5,
    easing: "ease-out"
  }
);

Исходное состояние задаётся заранее:

path.style.opacity = 0;
path.style.transform = "translateY(10px)";

Так формируется многослойный эффект, в котором линия не только «рисуется», но и появляется в пространстве.

Детерминированная анимация сложных иллюстраций

Для сложных SVG-сцен важна воспроизводимость анимации. Структура построения:

  • фиксация порядка path-элементов
  • вычисление длины до старта анимации
  • единая система задержек или timeline
  • унификация easing-функций

Пример унифицированного контроллера:

import { animate } from "motion";

function drawSequence(paths) {
  let offset = 0;

  paths.forEach((path) => {
    const length = path.getTotalLength();

    path.style.strokeDasharray = length;
    path.style.strokeDashoffset = length;

    animate(path, { strokeDashoffset: 0 }, {
      duration: 1,
      delay: offset,
      easing: "ease-out"
    });

    offset += 0.2;
  });
}

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