Прерывание анимаций

В Popmotion каждая анимация возвращает контроллер — объект, через который осуществляется управление процессом воспроизведения. Именно он отвечает за запуск, остановку и завершение анимации.

Контроллер создаётся при вызове анимационных функций:

  • animate()
  • tween()
  • spring()
  • decay()

и содержит методы управления состоянием выполнения.

Ключевая особенность архитектуры Popmotion заключается в том, что анимация не «живёт сама по себе» — она полностью управляется внешним контроллером, что делает прерывание естественной частью системы, а не исключением.


Состояния выполнения и переходы

Анимация в Popmotion проходит через несколько состояний:

  • playing — активное выполнение
  • paused — приостановка без сброса прогресса
  • stopped — полное прерывание выполнения
  • completed — завершение по достижению цели

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

Особенность состояния stopped заключается в том, что оно не эквивалентно завершению: анимация может быть прервана на любом кадре без вызова логики завершения.


Базовое прерывание через stop()

Основной механизм прерывания — метод stop().

const controls = animate({
  from: 0,
  to: 100,
  duration: 2000,
  onUpdate: v => console.log(v)
});

controls.stop();

После вызова:

  • цикл обновления останавливается
  • дальнейшие кадры не обрабатываются
  • значение фиксируется на текущем состоянии
  • onComplete не вызывается

Это ключевое отличие от естественного завершения анимации.


Разница между stop и complete

Методы stop() и complete() часто путаются, хотя их поведение принципиально различается.

  • stop() — аварийное прерывание без завершения логики
  • complete() — принудительное завершение с вызовом финальных колбэков
controls.complete();

При использовании complete():

  • анимация мгновенно переходит в конечное состояние
  • вызывается onComplete
  • внутренние значения фиксируются как завершённые

Прерывание при запуске новой анимации

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

Типичный паттерн:

let controls;

function startAnimation() {
  if (controls) controls.stop();

  controls = animate({
    from: 0,
    to: 300,
    onUpdate: v => {
      element.style.transform = `translateX(${v}px)`;
    }
  });
}

Здесь важно, что без явного stop() предыдущая анимация продолжит обновлять состояние, что приведёт к конфликту значений.


Прерывание внутри цепочек анимаций

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

При использовании последовательных вызовов:

const controls = animate({
  from: 0,
  to: 100,
  onComplete: () => {
    animate({
      from: 100,
      to: 200
    });
  }
});

прерывание первого контроллера не затрагивает вложенные анимации, если они уже были запущены.

Для полного контроля требуется хранить ссылки на все активные контроллеры и централизованно их останавливать.


Массовое прерывание анимаций

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

const activeAnimations = new Set();

function runAnimation() {
  const controls = animate({
    from: 0,
    to: 1,
    onUpdate: v => {}
  });

  activeAnimations.add(controls);

  controls.onS top = () => {
    activeAnimations.delete(controls);
  };
}

function stopAll() {
  activeAnimations.forEach(c => c.stop());
  activeAnimations.clear();
}

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


Прерывание spring и decay анимаций

Анимации типа spring и decay обладают инерцией и физической моделью, поэтому их остановка имеет особенности.

spring

const controls = spring({
  from: 0,
  to: 500,
  stiffness: 200
});

controls.stop();

Остановка spring:

  • прекращает физический расчёт
  • фиксирует текущее положение
  • не доводит систему до точки равновесия

decay

const controls = decay({
  from: 500,
  velocity: 1000
});

controls.stop();

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


Перезапуск после прерывания

После stop() анимацию нельзя «продолжить» в исходном виде. Вместо этого создаётся новый контроллер с обновлённым стартовым состоянием.

controls.stop();

animate({
  from: currentValue,
  to: 1000
});

Это связано с тем, что Popmotion не хранит историю траектории — только текущее состояние.


Прерывание через внешние события

Часто анимации прерываются не программно, а внешними событиями:

  • движение мыши
  • начало drag-операции
  • скролл
  • изменение состояния UI

Пример:

const controls = animate({
  from: 0,
  to: 400
});

window.addEventListener('wheel', () => {
  controls.stop();
});

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


Безопасное прерывание и защита от гонок

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

Решение заключается в «версионном контроле»:

let version = 0;

function animateBox() {
  const current = ++version;

  animate({
    from: 0,
    to: 300,
    onUpdate: v => {
      if (current !== version) return;
      box.style.transform = `translateX(${v}px)`;
    }
  });
}

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


Утечки и очистка ресурсов

Хотя Popmotion сам по себе не создаёт тяжёлых утечек памяти, неконтролируемые анимации могут:

  • продолжать вызывать onUpdate
  • удерживать ссылки на DOM-элементы
  • блокировать сборщик мусора

Поэтому любое прерывание должно сопровождаться:

  • stop() для контроллера
  • удалением ссылок из хранилищ
  • отменой подписок на события

Прерывание как часть архитектуры взаимодействий

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

Типовые сценарии:

  • hover отменяет движение
  • drag отменяет инерцию
  • новый переход отменяет предыдущий tween
  • изменение маршрута UI сбрасывает все spring-анимации

В таких системах контроллеры Popmotion используются как временные процессы, которые постоянно пересоздаются и прерываются в зависимости от контекста состояния интерфейса.