Анимации в интерфейсах редко существуют как одиночные переходы от точки A к точке B. Чаще они представляют собой систему режимов: «закрыто», «открыто», «наведение», «активно», «перетаскивание», «возврат». Именно это делает конечные автоматы (state machines) естественным инструментом для управления движением.
В контексте Popmotion подход через состояния решает ключевую проблему императивной анимации: рассинхронизацию логики интерфейса и визуального поведения. Вместо набора разрозненных tween’ов появляется единая модель переходов.
Любая анимационная система на основе конечного автомата опирается на три элемента:
Минимальная модель:
idle — покойentering — появлениеactive — активное состояниеleaving — исчезновениеКаждое состояние описывает не только визуальный вид, но и допустимые переходы.
Пример структуры:
const machine = {
initial: 'idle',
states: {
idle: {
on: {
MOUNT: 'entering'
}
},
entering: {
on: {
DONE: 'active'
}
},
active: {
on: {
UNMOUNT: 'leaving'
}
},
leaving: {
on: {
DONE: 'idle'
}
}
}
};
Такая структура отделяет поведение от реализации анимации, что критично для масштабируемых интерфейсов.
Popmotion предоставляет низкоуровневые примитивы движения:
tween — интерполяция значенийspring — физически корректное движениеphysics — движение с силамиstyler — привязка к DOMState machine не заменяет их, а управляет их запуском.
Суть интеграции: состояние определяет, какой motion-процесс активен.
Рассмотрим переход «появление элемента».
import { tween, styler } from 'popmotion';
const box = document.querySelector('.box');
const boxStyler = styler(box);
function enter() {
return tween({
from: { opacity: 0, y: 50 },
to: { opacity: 1, y: 0 },
duration: 300
}).start(v => boxStyler.set(v));
}
Теперь добавляется состояние:
let state = 'idle';
function send(event) {
switch (state) {
case 'idle':
if (event === 'MOUNT') {
state = 'entering';
enter().then(() => {
state = 'active';
});
}
break;
}
}
Проблема такого подхода — рост сложности при увеличении числа переходов.
State machine позволяет убрать условную логику из кода исполнения и заменить её таблицей переходов.
Добавляется универсальный диспетчер:
function createMachine(config) {
let current = config.initial;
function send(event) {
const nextState = config.states[current].on?.[event];
if (nextState) {
current = nextState;
config.actions?.[current]?.(send);
}
}
return { send };
}
Теперь анимации становятся реакцией на вход в состояние.
Каждое состояние может запускать свою анимацию.
const machine = createMachine({
initial: 'idle',
states: {
idle: {
on: {
MOUNT: 'entering'
}
},
entering: {
on: {
DONE: 'active'
}
},
active: {
on: {
UNMOUNT: 'leaving'
}
}
},
actions: {
entering(send) {
tween({
from: 0,
to: 1,
duration: 400
}).start({
update: v => boxStyler.set({ opacity: v }),
complete: () => send('DONE')
});
},
leaving(send) {
tween({
from: 1,
to: 0,
duration: 250
}).start({
update: v => boxStyler.set({ opacity: v }),
complete: () => send('DONE')
});
}
}
});
Важное свойство этой модели — отсутствие внешнего контроля над анимацией. Состояние само определяет жизненный цикл движения.
В UI часто возникает ситуация: новое событие приходит до завершения текущей анимации.
Например:
Без state machine возникает конфликт tween-процессов.
Решение — хранение ссылки на активную анимацию:
let currentAnimation = null;
function safeStart(animation, send) {
if (currentAnimation) currentAnimation.stop();
currentAnimation = animation.start({
complete: () => send('DONE')
});
}
Теперь состояния становятся предсказуемыми даже при частых переключениях.
В сложных интерфейсах одного уровня недостаточно.
Пример:
modal
closedopeningopenclosingвнутри modal:
content-loadingcontent-readyИерархия позволяет разделить ответственность.
const machine = {
initial: 'closed',
states: {
closed: {
on: { OPEN: 'opening' }
},
opening: {
on: { DONE: 'open' }
},
open: {
on: {
CLOSE: 'closing',
LOAD: 'loading'
},
states: {
loading: {
on: { READY: 'ready' }
},
ready: {}
}
}
}
};
Popmotion в таком случае управляет только визуальными переходами, а логика остаётся в структуре состояний.
State machine особенно полезна при координации нескольких Popmotion-потоков.
Пример: карточка одновременно изменяет:
function enter() {
tween({ from: 0, to: 1, duration: 300 }).start(v =>
boxStyler.set({
opacity: v,
scale: 0.8 + v * 0.2,
y: 30 * (1 - v)
})
);
}
При добавлении состояния появляется гарантия согласованного старта всех эффектов:
actions: {
entering(send) {
const anim = tween({
from: 0,
to: 1,
duration: 400
});
anim.start({
update: v => boxStyler.set({
opacity: v,
scale: 0.9 + v * 0.1,
y: 20 * (1 - v)
}),
complete: () => send('DONE')
});
}
}
Все параметры движения теперь зависят от одного источника времени.
Ключевое свойство state machines — детерминированность.
Для одного состояния и события всегда существует один результат.
Это особенно важно в анимациях:
Popmotion здесь выступает как «движок исполнения», а state machine — как «контроллер логики».
Состояния часто привязываются к UI-событиям:
draggingsettlingparallaxПример:
element.addEventListener('pointerdown', () => send('DRAG_START'));
element.addEventListener('pointerup', () => send('DRAG_END'));
Внутри состояний запускается физическая анимация:
dragging: {
on: {
DRAG_END: 'settling'
},
actions: () => {
physics({
from: currentPosition,
velocity: currentVelocity,
friction: 0.8
}).start(v => boxStyler.set({ x: v }));
}
}
State machine разделяет систему на уровни:
Такой разрез устраняет смешение обязанностей, которое обычно возникает в ручных анимациях.
При увеличении числа анимационных сценариев без state machine возникает экспоненциальный рост сложности:
С конечным автоматом рост линейный: добавляется только новое состояние и его переходы.
Несколько state machines могут работать совместно.
Пример:
Каждая управляет своей областью, не вмешиваясь в остальные.
const modalMachine = createMachine(...);
const contentMachine = createMachine(...);
Связь происходит через события, а не через прямые вызовы анимаций.
State machine формирует строгую модель:
событие → состояние → эффект → новое состояние
Popmotion выполняет только «эффект», не принимая решений о переходах.
Такой подход делает анимационную систему предсказуемой даже при высокой плотности взаимодействий и сложной композиции интерфейса