В Popmotion каждая анимация возвращает контроллер — объект, через который осуществляется управление процессом воспроизведения. Именно он отвечает за запуск, остановку и завершение анимации.
Контроллер создаётся при вызове анимационных функций:
animate()tween()spring()decay()и содержит методы управления состоянием выполнения.
Ключевая особенность архитектуры Popmotion заключается в том, что анимация не «живёт сама по себе» — она полностью управляется внешним контроллером, что делает прерывание естественной частью системы, а не исключением.
Анимация в Popmotion проходит через несколько состояний:
Переход между состояниями управляется методами контроллера и влияет на то, вызываются ли колбэки, продолжается ли интерполяция и сохраняется ли внутреннее состояние прогресса.
Особенность состояния stopped заключается в том, что оно
не эквивалентно завершению: анимация может быть прервана на любом кадре
без вызова логики завершения.
Основной механизм прерывания — метод stop().
const controls = animate({
from: 0,
to: 100,
duration: 2000,
onUpdate: v => console.log(v)
});
controls.stop();
После вызова:
onComplete не вызываетсяЭто ключевое отличие от естественного завершения анимации.
Методы 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 обладают
инерцией и физической моделью, поэтому их остановка имеет
особенности.
const controls = spring({
from: 0,
to: 500,
stiffness: 200
});
controls.stop();
Остановка spring:
const controls = decay({
from: 500,
velocity: 1000
});
controls.stop();
Decay особенно чувствителен к прерыванию, так как его движение полностью зависит от текущей скорости. При остановке скорость не сохраняется, и дальнейшее движение невозможно без нового запуска.
После stop() анимацию нельзя «продолжить» в исходном
виде. Вместо этого создаётся новый контроллер с обновлённым стартовым
состоянием.
controls.stop();
animate({
from: currentValue,
to: 1000
});
Это связано с тем, что Popmotion не хранит историю траектории — только текущее состояние.
Часто анимации прерываются не программно, а внешними событиями:
Пример:
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Поэтому любое прерывание должно сопровождаться:
stop() для контроллераВ интерфейсах с высокой динамикой прерывание анимаций становится не исключением, а основным механизмом управления состоянием.
Типовые сценарии:
В таких системах контроллеры Popmotion используются как временные процессы, которые постоянно пересоздаются и прерываются в зависимости от контекста состояния интерфейса.