Функции-генераторы в JavaScript представляют собой механизм
пошагового выполнения логики с возможностью приостанавливать и
возобновлять поток исполнения через yield. В контексте
анимаций на базе Motion One они превращаются в удобный инструмент
оркестрации сложных последовательностей, где каждая стадия анимации
становится управляемым состоянием, а переходы между состояниями — явными
точками контроля.
Основная ценность генераторов в анимационной архитектуре заключается в том, что они позволяют описывать временные сценарии линейно, не превращая код в каскад вложенных колбэков или громоздкие цепочки промисов. При этом Motion One обеспечивает низкоуровневое выполнение анимаций, а генераторы формируют надстройку, отвечающую за сценарную композицию.
Функция-генератор создаётся с использованием синтаксиса
function*. Каждый вызов yield фиксирует
момент, в котором управление передаётся внешнему исполнителю.
function* sequence() {
yield 1;
yield 2;
yield 3;
}
В анимационном контексте каждое значение, возвращаемое через
yield, может интерпретироваться как команда: запуск
анимации, ожидание завершения, пауза, смена состояния.
При интеграции с Motion One структура генератора перестаёт быть просто итератором значений и превращается в описатель сценария движения интерфейса.
Базовый API Motion One предоставляет функцию animate,
которая запускает анимацию элемента:
import { animate } from "motion";
animate(".box", { x: 200 }, { duration: 0.6 });
Возвращаемый объект анимации позволяет отслеживать состояние
выполнения. Это становится ключевым моментом при интеграции с
генераторами: yield может использоваться для ожидания
завершения анимации.
import { animate } from "motion";
function* animationFlow() {
const a1 = animate(".box", { x: 200 }, { duration: 0.5 });
yield a1;
const a2 = animate(".box", { y: 150 }, { duration: 0.5 });
yield a2;
const a3 = animate(".box", { rotate: 180 }, { duration: 0.5 });
yield a3;
}
Здесь генератор описывает линейную последовательность трансформаций.
Однако сам по себе генератор не исполняет анимации — требуется
исполнитель, который интерпретирует yield как сигнал
ожидания завершения.
Для превращения генератора в работающий механизм используется функция-раннер, которая рекурсивно обрабатывает итератор:
function runGenerator(gen) {
const iterator = gen();
function step(result) {
const { value, done } = iterator.next(result);
if (done) return;
if (value && typeof value.finished === "object") {
value.finished.then(() => step());
} else {
step();
}
}
step();
}
Motion One возвращает объект анимации, содержащий промис
finished, который сигнализирует завершение. Это позволяет
синхронизировать генератор с реальным временем выполнения анимации.
Ключевой механизм связывания генераторов и Motion One заключается в
использовании finished:
const animation = animate(".box", { scale: 1.5 }, { duration: 0.4 });
animation.finished.then(() => {
// переход к следующему шагу генератора
});
Таким образом генератор перестаёт быть абстракцией и превращается в последовательный диспетчер анимаций.
При усложнении интерфейса линейные цепочки становятся недостаточными. Генераторы позволяют выразить сценарий как последовательность шагов с промежуточными состояниями:
function* modalSequence() {
yield animate(".overlay", { opacity: 1 }, { duration: 0.2 });
yield animate(".modal", { scale: 1, opacity: 1 }, { duration: 0.3 });
yield animate(".content", { y: 0 }, { duration: 0.4 });
yield animate(".buttons", { opacity: 1 }, { duration: 0.2 });
}
Каждый yield фиксирует логическую стадию интерфейсного
поведения, а не просто визуальное изменение.
Motion One позволяет запускать несколько анимаций одновременно. В
генераторной модели это достигается через Promise.all.
function* parallelSequence() {
const group = Promise.all([
animate(".box1", { x: 100 }, { duration: 0.5 }).finished,
animate(".box2", { x: -100 }, { duration: 0.5 }).finished
]);
yield group;
yield animate(".box3", { scale: 2 }, { duration: 0.4 });
}
В данном случае yield работает не только с отдельной
анимацией, но и с агрегированным состоянием выполнения.
Motion One предоставляет функцию timeline, которая уже
сама по себе описывает последовательность анимаций. Однако генераторы
дают дополнительный уровень динамики — возможность программно
формировать timeline.
import { timeline } from "motion";
function* dynamicTimeline() {
const tl = timeline([
[".box", { x: 100 }, { duration: 0.4 }],
[".box", { y: 100 }, { duration: 0.4 }]
]);
yield tl;
}
В отличие от статического timeline, генератор может изменять структуру в зависимости от внешнего состояния.
Одним из ключевых преимуществ генераторов является возможность включать условную логику прямо в сценарий анимации.
function* conditionalFlow(isExpanded) {
if (isExpanded) {
yield animate(".panel", { height: 300 }, { duration: 0.3 });
yield animate(".text", { opacity: 1 }, { duration: 0.2 });
} else {
yield animate(".text", { opacity: 0 }, { duration: 0.2 });
yield animate(".panel", { height: 0 }, { duration: 0.3 });
}
}
Таким образом один генератор может описывать сразу несколько вариантов поведения интерфейса.
Генераторы позволяют формировать анимации на основе циклов, что особенно полезно при работе с коллекциями элементов.
function* staggered(items) {
for (let i = 0; i < items.length; i++) {
yield animate(items[i], { opacity: 1, y: 0 }, { duration: 0.3 });
}
}
Хотя Motion One поддерживает stagger как встроенную функциональность, генераторы дают полный контроль над порядком и логикой запуска.
В генераторной модели задержка становится равноправной частью сценария:
const wait = (ms) =>
new Promise(resolve => setTimeout(resolve, ms));
function* delayedFlow() {
yield animate(".box", { x: 100 }, { duration: 0.4 });
yield wait(300);
yield animate(".box", { y: 100 }, { duration: 0.4 });
}
Такая структура позволяет включать временные паузы без привязки к анимационным объектам.
Генераторы можно вкладывать друг в друга, создавая переиспользуемые сценарии.
function* fadeIn(el) {
yield animate(el, { opacity: 1 }, { duration: 0.3 });
}
function* slideUp(el) {
yield animate(el, { y: 0 }, { duration: 0.4 });
}
function* composed() {
yield* fadeIn(".box");
yield* slideUp(".box");
}
Конструкция yield* обеспечивает делегирование управления
другому генератору, формируя модульную архитектуру анимаций.
Генераторы могут принимать входные параметры и использовать их как часть сценария.
function* moveWithState(state) {
const distance = state.speed * 100;
yield animate(".box", { x: distance }, { duration: 0.5 });
}
Таким образом анимация становится функцией состояния приложения, а не статическим описанием.
Motion One отвечает за физику и исполнение анимации, а генераторы формируют поведенческий слой. Эта архитектура разделяет ответственность:
yield и синхронизация с
finishedТакое разделение позволяет масштабировать сложные интерфейсы без потери управляемости.
Motion One поддерживает отмену анимации через метод
stop. Это можно встроить в генераторную модель.
function* cancellableFlow(signal) {
const anim = animate(".box", { x: 200 }, { duration: 1 });
yield anim;
if (signal.aborted) {
anim.stop();
return;
}
yield animate(".box", { y: 200 }, { duration: 0.5 });
}
Сигналы позволяют внешнему коду влиять на выполнение сценария, не разрушая структуру генератора.
В реальных приложениях интерфейс часто требует комбинации последовательных, параллельных и условных анимаций. Генераторы позволяют описывать такие сценарии в одном потоке управления.
function* complexUI(state) {
yield animate(".header", { y: 0 }, { duration: 0.4 });
const content = animate(".content", { opacity: 1 }, { duration: 0.3 });
const sidebar = animate(".sidebar", { x: 0 }, { duration: 0.3 });
yield Promise.all([content.finished, sidebar.finished]);
if (state.hasNotification) {
yield animate(".badge", { scale: 1.2 }, { duration: 0.2 });
}
}
Такая модель сохраняет читаемость даже при увеличении количества анимационных зависимостей.