Последовательные анимации в Motion One представляют собой способ организации нескольких анимационных переходов так, чтобы каждый следующий запускался строго после завершения предыдущего. В отличие от параллельного исполнения, где несколько эффектов происходят одновременно, последовательный подход строит временную цепочку, формируя управляемый поток визуальных изменений.
Ключевая идея заключается в том, что анимация перестаёт быть одиночным действием и становится частью сценария, где каждый шаг зависит от состояния предыдущего.
Основные характеристики последовательных анимаций:
setTimeoutФункция animate в Motion One возвращает объект
управления анимацией, который содержит Promise-интерфейс. Это позволяет
выстраивать цепочки выполнения через await или
then.
import { animate } from "motion";
const box = document.querySelector(".box");
animate(box, { opacity: 0, transform: "translateY(20px)" }, { duration: 0.4 })
.finished
.then(() => {
animate(box, { opacity: 1, transform: "translateY(0px)" }, { duration: 0.4 });
});
Использование .finished превращает анимацию в
завершённое событие, после которого можно безопасно запускать следующий
шаг.
Особенности подхода:
Более структурированный способ организации цепочек строится на
async/await, что делает код линейным и предсказуемым.
import { animate } from "motion";
const box = document.querySelector(".box");
async function sequence() {
await animate(box, { x: 100 }, { duration: 0.3 }).finished;
await animate(box, { y: 100 }, { duration: 0.3 }).finished;
await animate(box, { rotate: 180 }, { duration: 0.3 }).finished;
await animate(box, { opacity: 0 }, { duration: 0.3 }).finished;
}
sequence();
Такой подход делает последовательность визуально идентичной порядку выполнения.
Преимущества:
Последовательные анимации часто разбиваются на логические блоки, которые затем комбинируются в более сложные сценарии.
const fadeOut = (el) =>
animate(el, { opacity: 0 }, { duration: 0.2 }).finished;
const move = (el) =>
animate(el, { x: 200 }, { duration: 0.4 }).finished;
const fadeIn = (el) =>
animate(el, { opacity: 1 }, { duration: 0.2 }).finished;
async function run(el) {
await fadeOut(el);
await move(el);
await fadeIn(el);
}
Такая декомпозиция превращает анимации в переиспользуемые строительные блоки.
Хотя stagger чаще применяется для параллельных групп, он
также может использоваться как инструмент создания
псевдопоследовательностей внутри коллекций элементов.
import { animate, stagger } from "motion";
const items = document.querySelectorAll(".item");
animate(
items,
{ opacity: [0, 1], y: [20, 0] },
{
delay: stagger(0.1),
duration: 0.4
}
);
В этом случае элементы не выполняются строго один за другим, но создаётся эффект каскадной последовательности.
Варианты управления порядком:
При необходимости динамического управления порядок анимаций формируется через циклы и массивы.
import { animate } from "motion";
const steps = [
(el) => animate(el, { x: 100 }).finished,
(el) => animate(el, { y: 100 }).finished,
(el) => animate(el, { rotate: 90 }).finished
];
async function runSequence(el) {
for (const step of steps) {
await step(el);
}
}
Такой подход особенно полезен при:
В Motion One доступен механизм временной шкалы, позволяющий задавать сложные последовательности с точным контролем таймингов.
import { timeline } from "motion";
const sequence = timeline([
["#box", { x: 100 }, { duration: 0.3 }],
["#box", { y: 100 }, { duration: 0.3 }],
["#box", { rotate: 180 }, { duration: 0.3 }]
]);
sequence.finished.then(() => {
console.log("sequence complete");
});
Timeline позволяет:
awaitПоследовательность можно усложнять за счёт относительных смещений времени.
timeline([
["#box", { x: 100 }, { duration: 0.3 }],
["#box", { y: 100 }, { duration: 0.3, at: "+0.1" }],
["#box", { rotate: 180 }, { duration: 0.3, at: "+0.2" }]
]);
Параметр at управляет моментом старта относительно
предыдущих шагов.
Типы смещений:
"+0.1" — небольшая задержка после предыдущего шага"<" — старт одновременно с предыдущимПоследовательные анимации часто включают несколько DOM-элементов, которые должны участвовать в едином сценарии.
timeline([
[".title", { opacity: 1, y: 0 }, { duration: 0.4 }],
[".subtitle", { opacity: 1, y: 0 }, { duration: 0.4, at: "+0.2" }],
[".button", { opacity: 1, scale: 1 }, { duration: 0.4, at: "+0.2" }]
]);
Такой подход позволяет строить интерфейсные сцены:
Каждая анимация и timeline возвращают контроллер, позволяющий управлять процессом.
Основные операции:
const controls = animate(".box", { x: 300 }, { duration: 2 });
setTimeout(() => {
controls.pause();
}, 500);
setTimeout(() => {
controls.play();
}, 1000);
Контроллеры особенно важны при последовательных сценариях, где требуется вмешательство пользователя или событийная логика.
Последовательные анимации часто зависят от состояния интерфейса или данных.
async function run(el, state) {
await animate(el, { opacity: 0.5 }).finished;
if (state.active) {
await animate(el, { scale: 1.2 }).finished;
} else {
await animate(el, { scale: 0.8 }).finished;
}
await animate(el, { opacity: 1 }).finished;
}
Такой подход превращает анимации в реактивный сценарий, управляемый состоянием.
Последовательные анимации часто применяются в многошаговых интерфейсах, где каждый шаг открывает следующий.
async function wizard() {
await animate(".step1", { opacity: 1 }).finished;
await animate(".step2", { opacity: 1 }).finished;
await animate(".step3", { opacity: 1 }).finished;
}
Усложнённая версия включает возврат назад:
async function wizardBackwards() {
await animate(".step3", { opacity: 0 }).finished;
await animate(".step2", { opacity: 0 }).finished;
}
Неправильная организация последовательных анимаций приводит к типичным проблемам:
.finished при использовании
Promise-цепочекsetTimeout с animateИзбежание этих проблем достигается единым стилем управления: либо
через async/await, либо через timeline, но не
смешанным образом без необходимости.
При увеличении количества шагов важно минимизировать избыточные вызовы и объединять состояния.
Подходы:
animate(el, {
x: [0, 100, 200],
opacity: [1, 0.5, 1]
}, { duration: 0.6 });
Такой подход снижает накладные расходы и упрощает синхронизацию.