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

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

Синхронизация в этом контексте означает не просто одновременный старт, а согласованное управление фазами анимации: запуск, прогресс, завершение и повторное воспроизведение.

Ключевой момент — отсутствие встроенного глобального таймлайна. Это означает, что координация полностью переносится на уровень логики приложения.


Базовая модель нескольких экземпляров Vivus

Каждый SVG-элемент инициализируется отдельным экземпляром:

const iconA = new Vivus('iconA', {
  type: 'delayed',
  duration: 120,
  start: 'manual'
});

const iconB = new Vivus('iconB', {
  type: 'delayed',
  duration: 120,
  start: 'manual'
});

Установка start: 'manual' критична: автоматический запуск разрушает возможность синхронизации, так как анимации начинают существовать в разных временных точках.

Далее управление переносится в пользовательский слой:

iconA.play();
iconB.play();

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


Проблема временного дрейфа

Даже при одинаковых параметрах duration и type возможны расхождения:

  • различная сложность SVG-путей
  • разное количество сегментов
  • нагрузка на DOM
  • асинхронная инициализация

В результате анимации начинают «расходиться» уже после первых 20–30% прогресса.

Для устранения этого эффекта требуется переход к координированному управлению состоянием.


Синхронный запуск через общий триггер

Простейший уровень синхронизации строится через единый управляющий вызов:

function startAll() {
  iconA.play();
  iconB.play();
  iconC.play();
}

requestAnimationFrame(startAll);

Использование requestAnimationFrame позволяет запустить все экземпляры в одном визуальном кадре, минимизируя рассинхрон старта.

Однако такой подход не управляет фазами выполнения и не контролирует завершение.


Синхронизация через контроль завершения

Более точная модель строится через обработку завершения анимации. Vivus поддерживает callback завершения:

const iconA = new Vivus('iconA', {
  duration: 100,
  start: 'manual'
}, onComplete);

function onComplete() {
  console.log('Animation finished');
}

При нескольких элементах используется счётчик завершений:

let completed = 0;
const total = 3;

function handleComplete() {
  completed++;

  if (completed === total) {
    allFinished();
  }
}

function allFinished() {
  console.log('All animations synchronized end');
}

new Vivus('iconA', { duration: 100, start: 'manual' }, handleComplete);
new Vivus('iconB', { duration: 100, start: 'manual' }, handleComplete);
new Vivus('iconC', { duration: 100, start: 'manual' }, handleComplete);

requestAnimationFrame(() => {
  iconA.play();
  iconB.play();
  iconC.play();
});

Здесь достигается синхронизация финальной точки, но не промежуточного состояния.


Принцип единого временного контроллера

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

Идея заключается в том, чтобы не позволять каждому Vivus жить в своём темпе, а обновлять их состояние из общего источника времени.

const instances = [iconA, iconB, iconC];

let startTime = null;
const duration = 2000;

function animate(timestamp) {
  if (!startTime) startTime = timestamp;

  const progress = Math.min((timestamp - startTime) / duration, 1);

  instances.forEach(instance => {
    instance.setFrameProgress(progress);
  });

  if (progress < 1) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

Метод setFrameProgress в Vivus не является публичным в классической API-версии, поэтому в реальных проектах используется либо модифицированная сборка, либо управление через внутренние методы (stop, play, reset) в комбинации с сегментацией.


Синхронизация через одинаковую конфигурацию типов анимации

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

  • delayed
  • sync
  • oneByOne

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

const commonConfig = {
  type: 'sync',
  duration: 150,
  start: 'manual'
};

const iconA = new Vivus('iconA', commonConfig);
const iconB = new Vivus('iconB', commonConfig);

Режим sync обеспечивает одновременную отрисовку всех путей внутри одного SVG. При использовании на нескольких элементах он снижает визуальный разрыв между объектами, так как поведение становится более предсказуемым.


Каскадная синхронизация

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

iconA.play();

iconA.getStatus = function () {
  return this.getStatus ? this.getStatus() : 'unknown';
};

Практический вариант строится через цепочку callback:

new Vivus('iconA', { start: 'manual' }, () => {
  new Vivus('iconB', { start: 'manual' }, () => {
    new Vivus('iconC', { start: 'manual' });
  }).play();
}).play();

Такой подход создаёт эффект связанной сцены, где элементы раскрываются по очереди, но сохраняют единый ритм.


Согласование длительности и плотности штрихов

Даже при идентичной длительности визуальная синхронность может нарушаться из-за различий в SVG:

  • длина path
  • количество subpath
  • stroke-dasharray
  • stroke-width

Для устранения дисбаланса применяется нормализация:

const config = {
  type: 'delayed',
  duration: 120,
  animTimingFunction: Vivus.EASE
};

Уменьшение вариативности easing-функций помогает добиться визуального совпадения фаз.


Групповое управление состоянием

Для масштабных сцен вводится единая структура управления экземплярами:

class VivusGroup {
  constructor(instances) {
    this.instances = instances;
  }

  playAll() {
    requestAnimationFrame(() => {
      this.instances.forEach(i => i.play());
    });
  }

  stopAll() {
    this.instances.forEach(i => i.stop());
  }

  resetAll() {
    this.instances.forEach(i => i.reset());
  }
}

Использование группы позволяет свести синхронизацию к одному управляющему объекту, устраняя необходимость ручного контроля каждого экземпляра.


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

Часто запуск анимации должен совпадать с внешними событиями интерфейса:

  • появление блока в viewport
  • завершение загрузки данных
  • переключение вкладок
window.addEventListener('load', () => {
  group.playAll();
});

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

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      group.playAll();
    }
  });
});

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

Координация повторного воспроизведения

Повторная синхронизация требует полного сброса состояния:

group.resetAll();

requestAnimationFrame(() => {
  group.playAll();
});

Без явного reset возможны остаточные состояния stroke, что нарушает повторяемость сцены.


Стабилизация синхронности в сложных сценах

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

  • единый requestAnimationFrame для старта
  • минимизация DOM-операций до запуска
  • предварительная загрузка SVG в скрытом состоянии
  • унификация конфигурации Vivus-экземпляров
  • отказ от динамического изменения параметров после инициализации

Такая стратегия позволяет удерживать визуальную синхронность даже при десятках элементов, формируя цельную анимированную композицию без заметных рассогласований между SVG-компонентами