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

Интеграция Vivus в реальный проект начинается с выбора способа подключения библиотеки и определения того, как именно SVG-анимации будут управляться в рамках архитектуры приложения. Vivus может использоваться как автономный модуль, как часть сборки через npm или как UMD-библиотека в классическом подключении через <script>.

При установке через npm структура проекта обычно предполагает явное управление зависимостями:

npm install vivus

Далее библиотека импортируется в модуль:

import Vivus from 'vivus';

В случаях использования классического подключения:

<script src="vivus.min.js"></script>

В глобальной области появляется Vivus, который можно использовать напрямую.


Инициализация экземпляра и базовая интеграция

Ключевая точка интеграции — создание экземпляра анимации, привязанного к конкретному SVG-элементу:

const animation = new Vivus('svgElementId', {
  duration: 200,
  type: 'delayed'
});

Первый параметр может быть как идентификатором DOM-элемента, так и самим элементом:

const svg = document.querySelector('.logo');
const animation = new Vivus(svg, {
  duration: 150
});

В рамках интеграции с пользовательским кодом важно учитывать момент инициализации DOM. Если SVG создаётся динамически, вызов должен происходить только после вставки элемента в документ.


Интеграция с динамически создаваемым SVG

В современных приложениях SVG часто формируется на лету. В таком случае Vivus должен инициализироваться после полной вставки разметки:

function renderIcon(container) {
  container.innerHTML = `
    <svg id="dynamicIcon" viewBox="0 0 100 100">
      <path d="..." />
    </svg>
  `;

  new Vivus('dynamicIcon', {
    duration: 120
  });
}

При использовании шаблонизаторов или виртуального DOM важно гарантировать, что реальный DOM уже синхронизирован.


Управление жизненным циклом анимации

Vivus предоставляет базовые методы управления состоянием анимации, которые удобно встраиваются в пользовательскую логику приложения:

const v = new Vivus('icon', { duration: 200 });

v.stop();
v.play(1);
v.reset();
v.finish();

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

button.addEventListener('click', () => {
  v.reset();
  v.play();
});

Синхронизация с пользовательскими событиями

Vivus легко связывается с DOM-событиями, что позволяет использовать его как часть реактивного интерфейса:

menu.addEventListener('mouseenter', () => {
  iconAnimation.play(1);
});

menu.addEventListener('mouseleave', () => {
  iconAnimation.stop().reset();
});

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


Интеграция с прокруткой страницы

Одним из наиболее частых сценариев является запуск анимации при появлении элемента в зоне видимости. Для этого используется IntersectionObserver:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      animation.play(1);
    }
  });
});

observer.observe(document.querySelector('#logo'));

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


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

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

const animations = [];

document.querySelectorAll('.animated-svg').forEach((el) => {
  animations.push(new Vivus(el, { duration: 100 }));
});

Далее можно реализовать массовое управление:

function playAll() {
  animations.forEach(a => a.reset().play());
}

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


Интеграция с компонентными фреймворками

React

В React интеграция выполняется через useEffect, чтобы гарантировать наличие DOM:

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

function Logo() {
  const ref = useRef(null);

  useEffect(() => {
    const animation = new Vivus(ref.current, {
      duration: 150
    });

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

  return (
    <svg ref={ref} viewBox="0 0 100 100">
      <path d="..." />
    </svg>
  );
}

Важно избегать повторной инициализации при каждом рендере, поэтому зависимости useEffect должны быть пустыми или строго контролируемыми.


Vue

Во Vue интеграция часто выполняется через mounted:

export default {
  mounted() {
    this.animation = new Vivus(this.$refs.icon, {
      duration: 120
    });
  },
  beforeUnmount() {
    this.animation.stop();
  }
};

Использование ref позволяет напрямую работать с DOM-узлом без дополнительных запросов.


Интеграция с системами сборки

При использовании Webpack, Vite или Rollup Vivus обычно импортируется как ES-модуль. Однако важно учитывать, что библиотека работает только в браузерной среде, так как зависит от DOM.

Для предотвращения ошибок SSR:

let Vivus;

if (typeof window !== 'undefined') {
  Vivus = require('vivus');
}

Или динамический импорт:

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

Привязка к состоянию приложения

В архитектурах с централизованным состоянием (Redux, Zustand, Pinia) Vivus может быть частью side-effect слоя.

store.subscribe((state) => {
  if (state.logoActive) {
    animation.play();
  } else {
    animation.stop().reset();
  }
});

Это позволяет отделить визуальную логику от бизнес-логики и избежать прямых DOM-манипуляций в компонентах.


Динамическое обновление SVG и повторная инициализация

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

function updateSVG(newMarkup) {
  container.innerHTML = newMarkup;

  animation = new Vivus(container.querySelector('svg'), {
    duration: 140
  });
}

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


Оптимизация интеграции

При масштабировании важно учитывать:

  • количество одновременно анимируемых SVG
  • сложность path-структур
  • частоту повторных запусков
  • влияние на main thread

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


Взаимодействие с пользовательскими обёртками

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

class SvgAnimator {
  constructor(el, options) {
    this.vivus = new Vivus(el, options);
  }

  play() {
    this.vivus.reset().play();
  }

  stop() {
    this.vivus.stop();
  }
}

Такая обёртка позволяет интегрировать библиотеку в любую архитектуру без привязки к её API.


Управление таймингом и пользовательская логика

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

const duration = userSettings.speed === 'fast' ? 80 : 200;

const animation = new Vivus('icon', {
  duration
});

Такой подход связывает визуальную составляющую с параметрами приложения, обеспечивая адаптивное поведение интерфейса.


Интеграция с асинхронными потоками данных

В приложениях, где SVG зависит от данных API, инициализация Vivus откладывается до завершения загрузки:

fetch('/api/icon')
  .then(res => res.text())
  .then(svg => {
    container.innerHTML = svg;

    new Vivus(container.querySelector('svg'), {
      duration: 130
    });
  });

Это позволяет связать визуализацию с серверным контентом без промежуточных состояний.


Контроль повторного воспроизведения

Для интерфейсов с повторяющимися анимациями важно управлять циклом:

function loopAnimation(v) {
  v.reset().play(1);

  v.el.addEventListener('animationEnd', () => {
    setTimeout(() => v.reset().play(1), 1000);
  });
}

Хотя Vivus не предоставляет полноценного event API, контроль можно реализовать через DOM-события или таймеры.


Совмещение с другими анимационными системами

Vivus может работать совместно с CSS-анимациями или GSAP-подобными системами, если разграничить ответственность:

  • Vivus — отрисовка path stroke
  • CSS — трансформации и opacity
  • внешние библиотеки — сцены и таймлайн
gsap.to(container, {
  opacity: 1,
  duration: 0.5,
  onComplete: () => animation.play()
});

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