Vivus в React чаще всего используется как утилита для анимации SVG-путей, когда требуется эффект «прорисовки линии» (line drawing animation). В React интеграция этой библиотеки требует учёта жизненного цикла компонентов, управления DOM-узлами через refs и аккуратной работы с повторной инициализацией анимаций при обновлениях состояния.
React управляет DOM декларативно, тогда как Vivus работает императивно, напрямую изменяя SVG-элементы. Это создаёт ключевую точку взаимодействия: доступ к DOM должен происходить строго после монтирования компонента.
Основной механизм интеграции строится вокруг:
useRef для получения ссылки на SVG-элементuseEffect для запуска анимации после рендераVivus устанавливается как обычная зависимость:
npm install vivus
или
yarn add vivus
Импорт в React-компоненте:
import Vivus from "vivus";
SVG может быть:
img (не подходит для Vivus
напрямую)Основной паттерн — привязка 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 генерируется динамически, важно учитывать момент готовности DOM:
useEffect(() => {
if (!svgRef.current) return;
requestAnimationFrame(() => {
new Vivus(svgRef.current, { duration: 80 });
});
}, [paths]);
requestAnimationFrame помогает избежать ситуации, когда
пути ещё не рассчитаны браузером.
При использовании 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-уровень, но совместимость выше.
В 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
});
}, []);
Vivus поддерживает несколько режимов:
delayed — последовательная прорисовкаsync — одновременная анимация всех путейoneByOne — поочерёдное рисование элементовВ React выбор типа часто зависит от частоты обновления компонента. Например:
delayedsynconeByOneТипичная ошибка — отсутствие зависимостей:
useEffect(() => {
new Vivus(svgRef.current, { duration: 100 });
}, []);
Если SVG изменяется, а зависимость не указана, анимация не обновится.
Правильный подход:
useEffect(() => {
if (!svgRef.current) return;
new Vivus(svgRef.current, { duration: 100 });
}, [svgContent]);
При большом количестве 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();
}, []);
Причины:
<path> элементыdisplay: none при инициализацииПричины:
Причины:
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.
Анимация может зависеть от state:
useEffect(() => {
if (isActive && svgRef.current) {
new Vivus(svgRef.current, { duration: 100 });
}
}, [isActive]);
Такой подход позволяет синхронизировать визуальные эффекты с логикой приложения, сохраняя контроль React над поведением интерфейса.