Использование в React

Vivus в React чаще всего используется как утилита для анимации SVG-путей, когда требуется эффект «прорисовки линии» (line drawing animation). В React интеграция этой библиотеки требует учёта жизненного цикла компонентов, управления DOM-узлами через refs и аккуратной работы с повторной инициализацией анимаций при обновлениях состояния.

React управляет DOM декларативно, тогда как Vivus работает императивно, напрямую изменяя SVG-элементы. Это создаёт ключевую точку взаимодействия: доступ к DOM должен происходить строго после монтирования компонента.

Основной механизм интеграции строится вокруг:

  • useRef для получения ссылки на SVG-элемент
  • useEffect для запуска анимации после рендера
  • очистки или пересоздания экземпляра Vivus при изменении зависимостей

Установка и подключение

Vivus устанавливается как обычная зависимость:

npm install vivus

или

yarn add vivus

Импорт в React-компоненте:

import Vivus from "vivus";

SVG может быть:

  • встроенным (inline SVG)
  • загруженным как компонент (через SVGR)
  • подключённым через img (не подходит для Vivus напрямую)

Базовая интеграция через useRef

Основной паттерн — привязка SVG через ref:

import { useEffect, useRef } from "react";
import Vivus from "vivus";

export default function LogoAnimation() {
  const svgRef = useRef(null);

  useEffect(() => {
    if (!svgRef.current) return;

    const animation = new Vivus(svgRef.current, {
      duration: 120,
      type: "delayed",
      animTimingFunction: Vivus.EASE
    });

    return () => {
      animation.stop();
    };
  }, []);

  return (
    <svg ref={svgRef} viewBox="0 0 200 200">
      <path d="M10 10 L190 10 L190 190 L10 190 Z" />
    </svg>
  );
}

Ключевой момент — передача DOM-элемента, а не React-компонента. Vivus работает только с реальным SVG-узлом.

Управление повторным запуском анимации

При изменении состояния часто требуется перезапуск анимации. В этом случае важно пересоздавать экземпляр:

useEffect(() => {
  if (!svgRef.current) return;

  const instance = new Vivus(svgRef.current, {
    duration: 100,
    type: "sync"
  });

  return () => {
    instance.destroy?.();
  };
}, [someState]);

Особенность: Vivus не всегда предоставляет полноценный destroy, поэтому иногда требуется ручной сброс SVG.

Использование с динамическими SVG

Если SVG генерируется динамически, важно учитывать момент готовности DOM:

useEffect(() => {
  if (!svgRef.current) return;

  requestAnimationFrame(() => {
    new Vivus(svgRef.current, { duration: 80 });
  });
}, [paths]);

requestAnimationFrame помогает избежать ситуации, когда пути ещё не рассчитаны браузером.

Интеграция с компонентами SVG (SVGR)

При использовании SVGR SVG импортируется как React-компонент:

import Logo from "./logo.svg";

Но Vivus требует DOM-элемент. Решение — ref на корневой SVG:

import { useEffect, useRef } from "react";
import Vivus from "vivus";
import { ReactComponent as Logo } from "./logo.svg";

export default function App() {
  const svgRef = useRef(null);

  useEffect(() => {
    if (!svgRef.current) return;

    new Vivus(svgRef.current, { duration: 100 });
  }, []);

  return <Logo ref={svgRef} />;
}

Однако не все конфигурации SVGR позволяют прокидывать ref напрямую. Альтернативный подход — обёртка:

export default function LogoWrapper() {
  const ref = useRef(null);

  useEffect(() => {
    if (!ref.current) return;

    new Vivus(ref.current, { duration: 100 });
  }, []);

  return (
    <div ref={ref}>
      <Logo />
    </div>
  );
}

Недостаток — лишний DOM-уровень, но совместимость выше.

Работа в Next.js и SSR

В SSR-окружении важно учитывать отсутствие window и DOM. Vivus должен вызываться только на клиенте:

useEffect(() => {
  if (typeof window === "undefined") return;
  if (!svgRef.current) return;

  import("vivus").then(({ default: Vivus }) => {
    new Vivus(svgRef.current, { duration: 120 });
  });
}, []);

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

Контроль повторной инициализации

В React Strict Mode (особенно в разработке) useEffect может вызываться дважды. Это приводит к двойному запуску анимации.

Решение — хранение экземпляра:

const vivusInstance = useRef(null);

useEffect(() => {
  if (!svgRef.current) return;

  if (vivusInstance.current) {
    vivusInstance.current.stop?.();
  }

  vivusInstance.current = new Vivus(svgRef.current, {
    duration: 100
  });
}, []);

Типы анимации и их поведение в React

Vivus поддерживает несколько режимов:

  • delayed — последовательная прорисовка
  • sync — одновременная анимация всех путей
  • oneByOne — поочерёдное рисование элементов

В React выбор типа часто зависит от частоты обновления компонента. Например:

  • статические логотипы → delayed
  • интерактивные иконки → sync
  • сложные иллюстрации → oneByOne

Управление зависимостями эффекта

Типичная ошибка — отсутствие зависимостей:

useEffect(() => {
  new Vivus(svgRef.current, { duration: 100 });
}, []);

Если SVG изменяется, а зависимость не указана, анимация не обновится.

Правильный подход:

useEffect(() => {
  if (!svgRef.current) return;

  new Vivus(svgRef.current, { duration: 100 });
}, [svgContent]);

Оптимизация производительности

При большом количестве SVG-анимаций важно избегать:

  • одновременного запуска нескольких Vivus-инстансов
  • повторной инициализации без необходимости
  • анимации скрытых элементов

Практика оптимизации:

  • запуск анимации только при появлении в viewport (IntersectionObserver)
  • отключение анимации вне экрана
  • кэширование SVG-структур

Пример ленивого запуска:

useEffect(() => {
  const observer = new IntersectionObserver(([entry]) => {
    if (entry.isIntersecting && svgRef.current) {
      new Vivus(svgRef.current, { duration: 120 });
    }
  });

  if (svgRef.current) observer.observe(svgRef.current);

  return () => observer.disconnect();
}, []);

Частые проблемы интеграции

SVG не анимируется

Причины:

  • отсутствуют <path> элементы
  • SVG оптимизирован (SVGO удалил stroke-данные)
  • элемент скрыт display: none при инициализации

Анимация запускается дважды

Причины:

  • React Strict Mode
  • повторный рендер из-за изменения state
  • отсутствие очистки предыдущего инстанса

Некорректная прорисовка

Причины:

  • пути не имеют stroke
  • используется fill вместо stroke
  • сложные трансформации внутри SVG

Работа с кастомными параметрами

Vivus позволяет тонко управлять поведением:

new Vivus(svgRef.current, {
  duration: 150,
  type: "oneByOne",
  start: "autostart",
  delay: 20,
  pathTimingFunction: Vivus.EASE_OUT
});

В React такие параметры часто выносятся в props компонента, чтобы управлять анимацией декларативно.

Инкапсуляция в переиспользуемый компонент

Типовой паттерн — создание универсального компонента:

function AnimatedSvg({ children, duration = 100, type = "delayed" }) {
  const ref = useRef(null);

  useEffect(() => {
    if (!ref.current) return;

    new Vivus(ref.current, { duration, type });
  }, [duration, type]);

  return <svg ref={ref}>{children}</svg>;
}

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

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

React-способ пересоздания анимации:

<AnimatedSvg key={animationKey}>
  <path d="..." />
</AnimatedSvg>

Изменение key гарантирует полное размонтирование и пересоздание DOM, что упрощает контроль состояния Vivus.

Взаимодействие с состоянием React

Анимация может зависеть от state:

useEffect(() => {
  if (isActive && svgRef.current) {
    new Vivus(svgRef.current, { duration: 100 });
  }
}, [isActive]);

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