Reverse и forceRender

Библиотека Vivus строит анимацию SVG на основе управления параметром прогресса от 0 до 1, где:

  • 0 — начальное состояние (пути полностью скрыты через stroke-dashoffset)
  • 1 — финальное состояние (контуры полностью прорисованы)

Внутри движка каждый SVG-path преобразуется в набор параметров:

  • длина пути (getTotalLength)
  • stroke-dasharray
  • stroke-dashoffset

Анимация фактически сводится к изменению stroke-dashoffset по времени.


Reverse: инверсия направления анимации

Принцип работы reverse

Reverse в Vivus — это не отдельный тип анимации, а изменение направления изменения прогресса:

  • стандартное направление: 0 → 1
  • обратное направление: 1 → 0

При этом логика отрисовки остаётся той же, меняется только интерполяция прогресса.

Если в обычном режиме вычисление выглядит так:

offset = length * (1 - progress)

то в обратном режиме:

offset = length * progress

Способы запуска reverse-анимации

1. Обратное проигрывание через play с направлением

В экземпляре Vivus можно управлять направлением проигрывания:

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

// обычное проигрывание
vivus.play(1);

// обратное проигрывание
vivus.play(-1);

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


2. Переключение состояния после завершения анимации

Часто используется схема «вперёд-назад»:

const vivus = new Vivus('svg', {
  type: 'oneByOne',
  duration: 150,
  onComplete: function () {
    this.play(-1);
  }
});

При завершении анимации она автоматически запускается в обратную сторону.


3. Управление через прогресс вручную

В случаях, когда требуется точная синхронизация:

vivus.setFrameProgress(1);
vivus.play(-1);

или:

vivus.setFrameProgress(0.5);

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


Особенности reverse в разных режимах анимации

delayed

При type: 'delayed' каждый path получает задержку. В reverse:

  • задержки сохраняются
  • порядок выполнения визуально инвертируется
  • эффект «разворачивания» становится более выраженным

oneByOne

Для oneByOne порядок отрисовки строго последовательный. При reverse:

  • последовательность путей идёт в обратном порядке
  • визуально создаётся эффект «сборки назад»

sync

При sync все пути анимируются одновременно:

  • reverse влияет только на направление прогресса
  • визуальный эффект минимально отличается от forward

Ограничения reverse-режима

Reverse не всегда работает как «идеальное перемещение назад» в следующих случаях:

  • SVG был модифицирован после инициализации
  • изменены атрибуты stroke-dasharray вручную
  • используется selfDestroy: true
  • анимация прервана в нестабильном состоянии прогресса

Типовые паттерны использования reverse

Переключатель состояния (toggle animation)

let direction = 1;

button.addEventListener('click', () => {
  vivus.play(direction);
  direction *= -1;
});

Модель используется для иконок:

  • меню ↔︎ крест
  • play ↔︎ pause
  • загрузка ↔︎ завершение

Зацикленный маятник

const loop = function () {
  vivus.play(1);

  setTimeout(() => {
    vivus.play(-1);
  }, 2000);
};

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


Hover-инверсия

element.addEventListener('mouseenter', () => {
  vivus.play(1);
});

element.addEventListener('mouseleave', () => {
  vivus.play(-1);
});

Создаёт эффект «проявления и исчезновения» контура.


forceRender: принудительная отрисовка SVG

Назначение forceRender

forceRender используется для принудительного пересчёта и перерисовки SVG-путей без запуска анимации.

Основная задача — синхронизация визуального состояния с внутренней моделью Vivus.


Когда требуется forceRender

1. SVG скрыт при инициализации

Если SVG изначально находится в display: none:

  • браузер не вычисляет корректные длины путей
  • Vivus получает нулевые или некорректные значения

После отображения требуется:

vivus.forceRender();

2. Изменение размеров контейнера

При ресайзе:

  • меняется геометрия SVG
  • старые dash-значения становятся некорректными
window.addEventListener('resize', () => {
  vivus.forceRender();
});

3. Динамическая подмена SVG

Если содержимое SVG заменяется через DOM:

  • старые path-ссылки становятся невалидными
  • требуется пересчёт всех сегментов

4. После внешних изменений stroke

Например:

document.querySelectorAll('path').forEach(p => {
  p.setAttribute('stroke-width', 4);
});

vivus.forceRender();

Что делает forceRender внутри

При вызове выполняются следующие шаги:

  • пересчёт всех path.getTotalLength()
  • обновление stroke-dasharray
  • пересборка внутреннего массива путей
  • синхронизация текущего прогресса
  • принудительное обновление stroke-dashoffset

Фактически происходит «переинициализация без пересоздания экземпляра».


Отличие forceRender от play/reset

Метод Поведение
play() запускает анимацию
stop() останавливает
reset() сбрасывает прогресс
forceRender() пересчитывает геометрию SVG

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


Влияние forceRender на reverse-анимацию

При использовании reverse важно учитывать:

  • после forceRender направление сохраняется
  • но прогресс может быть пересчитан относительно новых параметров пути

Типичный сценарий:

vivus.play(-1);

setTimeout(() => {
  vivus.forceRender();
  vivus.play(-1);
}, 1000);

Частые ошибки при использовании forceRender

Вызов до полной загрузки SVG

Если SVG ещё не доступен в DOM:

  • пересчёт не выполняется корректно
  • часть путей может быть пропущена

Частый вызов в цикле resize

Постоянный вызов при каждом изменении размера окна приводит к:

  • лишней нагрузке на main thread
  • повторному вычислению длины всех path

Оптимизированный вариант:

let timeout;

window.addEventListener('resize', () => {
  clearTimeout(timeout);
  timeout = setTimeout(() => vivus.forceRender(), 150);
});

Комбинация reverse и forceRender в интерактивных интерфейсах

Реинициализация состояния при смене темы

themeToggle.addEventListener('click', () => {
  document.body.classList.toggle('dark');

  vivus.forceRender();
  vivus.play(-1);
});

Перезапуск анимации после динамической замены SVG

container.innerHTML = newSVG;

const vivus = new Vivus('svg', {
  type: 'delayed',
  duration: 120
});

vivus.forceRender();
vivus.play(1);

Интерактивный прогресс с обратной прокруткой

slider.addEventListener('input', (e) => {
  const value = e.target.value / 100;

  vivus.setFrameProgress(value);

  if (value < 0.2) {
    vivus.play(-1);
  }
});

Поведение в нестабильных состояниях

При комбинировании reverse и forceRender возможны состояния:

  • частично отрисованные пути
  • скачки прогресса
  • резкое изменение длины dashoffset

Такие эффекты связаны с тем, что forceRender пересчитывает геометрию, не дожидаясь завершения текущего кадра анимации.