Последовательные анимации в Popmotion строятся вокруг идеи управления временем и явного контроля над моментом старта каждого следующего движения. В отличие от параллельных анимаций, где несколько процессов выполняются одновременно, последовательный подход требует строгого порядка: одна анимация завершается, затем начинается следующая.
В Popmotion этот сценарий реализуется через несколько механизмов:
chain, timeline, а также комбинации
animate с ручным управлением жизненным циклом анимаций.
Любая последовательная анимация опирается на принцип завершённости предыдущего шага. Это означает, что система должна:
В Popmotion каждая анимация возвращает контроллер (animation controls), который позволяет отслеживать состояние и управлять запуском.
Функция chain позволяет объединять несколько анимаций в
строгую последовательность. Она принимает набор анимационных функций и
запускает их по очереди.
import { chain, animate, easeInOut } from "popmotion";
chain(
(complete) =>
animate({
from: 0,
to: 100,
duration: 500,
onUpdate: (v) => console.log("A:", v),
onComplete: complete
}),
(complete) =>
animate({
from: 100,
to: 200,
duration: 500,
onUpdate: (v) => console.log("B:", v),
onComplete: complete
}),
(complete) =>
animate({
from: 200,
to: 0,
duration: 500,
onUpdate: (v) => console.log("C:", v),
onComplete: complete
})
);
Каждая функция получает complete — callback, который
необходимо вызвать вручную для перехода к следующему шагу. Это даёт
полный контроль над последовательностью.
Последовательность можно строить без chain, используя
вложенные вызовы. Такой подход полезен, когда требуется динамическая
логика переходов.
import { animate } from "popmotion";
animate({
from: 0,
to: 100,
duration: 400,
onComplete: () => {
animate({
from: 100,
to: 200,
duration: 400,
onComplete: () => {
animate({
from: 200,
to: 50,
duration: 400
});
}
});
}
});
Такой стиль делает структуру менее декларативной, но позволяет внедрять условия между шагами, например:
timeline используется для описания сложных сценариев,
где важны не только последовательность, но и точное позиционирование во
времени.
import { timeline } from "popmotion";
timeline([
[0, (complete) => {
animate({
from: 0,
to: 100,
duration: 300,
onComplete: complete
});
}],
[300, (complete) => {
animate({
from: 100,
to: 200,
duration: 300,
onComplete: complete
});
}],
[600, (complete) => {
animate({
from: 200,
to: 0,
duration: 300,
onComplete: complete
});
}]
]);
Первый элемент каждого массива — это момент времени в миллисекундах, второй — функция анимации. Такой подход превращает анимации в временную карту, где каждое событие имеет точное положение.
Иногда последовательность проще выразить через задержки, чем через
цепочки. В Popmotion это достигается через комбинацию delay
внутри timeline или через ручные таймеры.
import { animate } from "popmotion";
const step = (from, to, duration, delay = 0, onComplete) => {
setTimeout(() => {
animate({
from,
to,
duration,
onComplete
});
}, delay);
};
step(0, 100, 400, 0, () => {
step(100, 200, 400, 0, () => {
step(200, 0, 400);
});
});
Такой подход часто используется в интеграции Popmotion с DOM, когда требуется синхронизация с другими системами тайминга.
Последовательные анимации редко существуют изолированно. Обычно они комбинируются с параллельными блоками.
Пример: сначала один элемент движется, затем одновременно изменяются два свойства другого элемента.
import { animate } from "popmotion";
animate({
from: 0,
to: 100,
duration: 400,
onComplete: () => {
animate({
from: 0,
to: 1,
duration: 300,
onUpdate: (opacity) => {
console.log("opacity:", opacity);
}
});
animate({
from: 50,
to: 150,
duration: 300,
onUpdate: (x) => {
console.log("x:", x);
}
});
}
});
Здесь последовательность управляет моментом запуска, но внутри шага создаётся параллельное поведение.
Сложные интерфейсы требуют условных переходов. Последовательная анимация в Popmotion может включать выбор следующего шага в зависимости от состояния предыдущего.
import { animate } from "popmotion";
let direction = "forward";
animate({
from: 0,
to: 100,
duration: 400,
onComplete: () => {
if (direction === "forward") {
animate({
from: 100,
to: 200,
duration: 400,
onComplete: () => {
direction = "backward";
}
});
} else {
animate({
from: 100,
to: 0,
duration: 400
});
}
}
});
Последовательность перестаёт быть линейной и превращается в граф состояний.
В некоторых реализациях Popmotion используется концепция
sequence, где набор анимаций описывается как массив
шагов.
import { animate } from "popmotion";
const sequence = [
{ from: 0, to: 100, duration: 300 },
{ from: 100, to: 200, duration: 300 },
{ from: 200, to: 0, duration: 300 }
];
const runSequence = (items, index = 0) => {
if (index >= items.length) return;
const item = items[index];
animate({
...item,
onComplete: () => runSequence(items, index + 1)
});
};
runSequence(sequence);
Такая структура удобна для декларативного описания UI-анимаций, особенно в компонентах интерфейса.
Последовательные анимации должны поддерживать возможность остановки. Это важно для интерфейсов, где пользователь может прервать действие.
import { animate } from "popmotion";
let cancelled = false;
const run = () => {
animate({
from: 0,
to: 100,
duration: 400,
onComplete: () => {
if (cancelled) return;
animate({
from: 100,
to: 200,
duration: 400,
onComplete: () => {
if (cancelled) return;
animate({
from: 200,
to: 0,
duration: 400
});
}
});
}
});
};
run();
setTimeout(() => {
cancelled = true;
}, 500);
Контроль состояния последовательности становится обязательным при сложных сценариях UI.
Несколько последовательностей могут запускаться независимо и пересекаться по времени. В этом случае важно учитывать, что каждая цепочка живёт в своём временном контексте.
import { animate } from "popmotion";
const sequenceA = () => {
animate({
from: 0,
to: 100,
duration: 500,
onComplete: () => {
animate({ from: 100, to: 200, duration: 500 });
}
});
};
const sequenceB = () => {
animate({
from: 50,
to: 150,
duration: 300,
onComplete: () => {
animate({ from: 150, to: 50, duration: 300 });
}
});
};
sequenceA();
sequenceB();
Такая модель используется для сложных интерфейсов, где разные элементы имеют собственные сценарии движения.
На практике последовательные анимации часто моделируются как конечные автоматы, где каждый шаг — это состояние, а переход определяется завершением анимации.
Такой подход упрощает масштабирование сложных анимационных систем и делает поведение предсказуемым.
const stateMachine = {
start: (next) => animate({ from: 0, to: 100, duration: 300, onComplete: next }),
middle: (next) => animate({ from: 100, to: 200, duration: 300, onComplete: next }),
end: () => animate({ from: 200, to: 0, duration: 300 })
};
stateMachine.start(() => {
stateMachine.middle(() => {
stateMachine.end();
});
});
Последовательность превращается в управляемую систему переходов, где каждый шаг изолирован и переиспользуем.