В Motion One управление последовательностью анимаций строится вокруг композиции независимых анимационных сущностей, которые могут исполняться строго последовательно, параллельно или в гибридных схемах. Основной принцип заключается в том, что каждая анимация возвращает управляющий объект (animation controls), позволяющий синхронизировать выполнение и выстраивать сложные цепочки без внешних таймеров.
Каждая анимация в Motion One представляет собой вызов
animate, возвращающий контроллер:
import { animate } from "motion";
const controls = animate(".box", { x: 200 });
Этот объект становится точкой управления состоянием выполнения. Последовательность формируется не внутри самой анимации, а через координацию нескольких контроллеров.
Анимации могут исполняться одна за другой через ожидание завершения
предыдущей. Контроллер анимации поддерживает finished,
который представляет Promise завершения:
const a = animate(".box-1", { x: 200 });
await a.finished;
const b = animate(".box-2", { x: 200 });
await b.finished;
const c = animate(".box-3", { x: 200 });
await c.finished;
Такая модель превращает последовательность в линейный поток исполнения, где каждая стадия строго зависит от завершения предыдущей.
Последовательность может быть задана не только порядком вызовов, но и
временными смещениями внутри единого сценария. В Motion One это
реализуется через delay и относительные offsets.
animate(".box-1", { x: 100 }, { duration: 0.5 });
animate(".box-2", { x: 100 }, { delay: 0.2, duration: 0.5 });
animate(".box-3", { x: 100 }, { delay: 0.4, duration: 0.5 });
Такой подход позволяет формировать псевдопоследовательность внутри параллельного исполнения.
Несмотря на независимость вызовов, несколько анимаций могут быть объединены в логическую группу исполнения:
const a1 = animate(".a", { x: 100 });
const a2 = animate(".b", { y: 100 });
const a3 = animate(".c", { scale: 1.2 });
await Promise.all([a1.finished, a2.finished, a3.finished]);
Здесь формируется параллельный блок, завершение которого определяется самым долгим элементом.
Ключевым инструментом для управления сложной последовательностью
является timeline. Он позволяет описывать порядок анимаций
декларативно, без ручного управления промисами и задержками.
import { timeline } from "motion";
timeline([
[".box-1", { x: 100 }, { duration: 0.4 }],
[".box-2", { y: 100 }, { duration: 0.4 }],
[".box-3", { scale: 1.2 }, { duration: 0.4 }]
]);
Каждый элемент массива представляет шаг последовательности. По умолчанию шаги идут строго один за другим.
Timeline поддерживает более гибкую модель расположения шагов во времени через offset-значения:
timeline([
[".box-1", { x: 100 }, { duration: 0.5 }],
[".box-2", { y: 100 }, { duration: 0.5 }, "-=0.2"],
[".box-3", { scale: 1.5 }, { duration: 0.5 }, "+=0.1"]
]);
"-=0.2" — начало раньше завершения предыдущего
шага"+=0.1" — задержка относительно конца предыдущего
шагаТакая модель позволяет строить перекрывающиеся и ритмические последовательности без ручного расчета времени.
Timeline допускает вложенные группы, где несколько анимаций стартуют как единый блок:
timeline([
[
[".a", { x: 100 }],
[".b", { x: 100 }]
],
[".c", { scale: 1.2 }]
]);
Внутренний массив интерпретируется как параллельная группа, а внешний — как последовательность блоков.
Для однотипных элементов используется stagger,
позволяющий распределить запуск по времени:
import { stagger, animate } from "motion";
animate(
".item",
{ opacity: 1, y: 0 },
{ delay: stagger(0.1) }
);
Каждый элемент получает индивидуальную задержку, формируя каскадную последовательность без явного перечисления узлов.
Stagger может быть модифицирован функцией:
stagger((index) => index * 0.05);
Это позволяет строить нелинейные распределения времени.
Каждая анимация или timeline возвращает контроллер, поддерживающий базовые операции управления:
const controls = animate(".box", { x: 200 });
controls.pause();
controls.play();
controls.stop();
controls.finish();
controls.cancel();
Эти методы позволяют изменять состояние выполнения без пересоздания анимации.
pause — фиксирует текущее состояниеplay — возобновляет выполнениеstop — завершает анимацию с фиксацией текущего
состоянияfinish — мгновенно завершает до конечного
состоянияcancel — полностью сбрасывает выполнениеПоследовательность не обязана быть статичной. Анимации могут создаваться и подключаться в рантайме, формируя адаптивные сценарии:
let sequence = [];
sequence.push(animate(".box-1", { x: 100 }));
sequence.push(animate(".box-2", { y: 100 }));
await Promise.all(sequence.map(a => a.finished));
Такой подход используется для сценариев, где порядок зависит от состояния интерфейса или данных.
Каждый контроллер поддерживает реакцию на завершение:
animate(".box", { x: 100 }).finished.then(() => {
animate(".box", { scale: 1.2 });
});
Это создаёт событийную последовательность, в которой следующий шаг зависит от завершения предыдущего, но не блокирует основной поток выполнения.
В реальных архитектурах последовательности часто комбинируются:
import { animate, timeline, stagger } from "motion";
timeline([
[".header", { opacity: 1 }, { duration: 0.3 }],
[
".items",
{ y: 0, opacity: 1 },
{ delay: stagger(0.1), duration: 0.4 }
],
[".footer", { opacity: 1 }, { duration: 0.3 }]
]);
Такая структура формирует иерархическую последовательность: блоки идут последовательно, элементы внутри блоков — каскадно.
Несколько timeline или наборов animate могут существовать одновременно, при этом конкурируя за визуальное состояние элементов. В таких случаях важна предсказуемость:
const t1 = timeline([
[".box", { x: 100 }]
]);
const t2 = timeline([
[".box", { y: 100 }]
]);
Последний запущенный сценарий фактически переопределяет предыдущий для тех же свойств, формируя приоритет по времени исполнения.
Последовательности могут быть переиспользованы как функции:
const slideIn = () =>
timeline([
[".panel", { x: 0, opacity: 1 }, { duration: 0.4 }],
[".content", { opacity: 1 }, { duration: 0.3 }]
]);
slideIn();
slideIn();
Это позволяет строить библиотеку анимационных сценариев как набор переиспользуемых модулей, где каждый модуль самодостаточен и не зависит от внешнего состояния исполнения.