Управление последовательностью

В 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 как центральный механизм композиции

Ключевым инструментом для управления сложной последовательностью является 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 как механизм массовой последовательности

Для однотипных элементов используется 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 });
});

Это создаёт событийную последовательность, в которой следующий шаг зависит от завершения предыдущего, но не блокирует основной поток выполнения.


Композиция сложных сценариев

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

  • timeline для глобального сценария
  • stagger для групповых эффектов
  • finished/promises для логической синхронизации
  • delay для локальных смещений
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();

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