В анимационных системах Popmotion переходы между состояниями строятся вокруг идеи управляемого изменения значений во времени: чисел, координат, цветов, углов, физически моделируемых величин. Состояние в этом контексте — это набор параметров в конкретный момент времени, а переход — процесс их преобразования от одного набора значений к другому через определённый закон движения.
Любая анимация в Popmotion опирается на три компонента:
Переход между состояниями можно представить как функцию:
При этом t нормализуется в диапазон от 0 до 1 в случае временных анимаций или становится непрерывным потоком в физически моделируемых системах.
Классический переход:
import { tween } from "popmotion";
tween({
from: 0,
to: 100,
duration: 600
}).start(v => {
console.log(v);
});
Здесь происходит линейное или сглаженное преобразование состояния от 0 к 100.
Линейная интерполяция — самый простой способ соединения двух состояний. Значение изменяется равномерно, без ускорений и замедлений.
Характеристика:
Используется для простых интерфейсных эффектов и вычислительных визуализаций.
tween({
from: { x: 0 },
to: { x: 300 },
duration: 400
}).start(({ x }) => {
element.style.transform = `translateX(${x}px)`;
});
Реальные интерфейсы редко используют чистую линейность. Переходы смягчаются функциями easing, которые изменяют скорость изменения состояния.
Основные классы easing:
Пример:
import { tween, easing } from "popmotion";
tween({
from: 0,
to: 1,
duration: 800,
ease: easing.easeInOut
}).start(v => {
element.style.opacity = v;
});
Easing трансформирует линейное время в нелинейную кривую, создавая ощущение инерции.
Физические модели переходов используются для имитации реального движения. Вместо фиксированной длительности применяется модель силы, скорости и сопротивления.
Пружинная модель описывает движение через параметры:
import { spring } from "popmotion";
spring({
from: 0,
to: 500,
stiffness: 120,
damping: 20
}).start(v => {
box.style.transform = `translateX(${v}px)`;
});
Поведение состояния становится зависимым не от времени, а от динамики системы.
Decay моделирует затухающее движение, где конечное состояние не задаётся явно.
import { decay } from "popmotion";
decay({
from: 0,
velocity: 500
}).start(v => {
scrollContainer.scrollLeft = v;
});
Состояние постепенно теряет энергию, приближаясь к покою.
В реальных интерфейсах состояние редко представляет собой одно число. Обычно это объект с несколькими параметрами:
const stateA = {
x: 0,
y: 0,
opacity: 0
};
const stateB = {
x: 300,
y: 200,
opacity: 1
};
Popmotion интерполирует каждый ключ отдельно:
import { tween } from "popmotion";
tween({
from: stateA,
to: stateB,
duration: 700
}).start(v => {
box.style.transform = `translate(${v.x}px, ${v.y}px)`;
box.style.opacity = v.opacity;
});
Такой подход позволяет описывать сложные переходы как единый процесс, а не набор независимых анимаций.
Механизм интерполяции отвечает за вычисление промежуточных значений между состояниями.
Для чисел используется линейная интерполяция:
Для объектов интерполяция выполняется по каждому полю отдельно.
Особое значение имеет согласованность типов данных: переходы должны сохранять структуру состояния на всём протяжении анимации.
При работе с интерфейсами часто требуется согласованное изменение нескольких параметров.
Пример: одновременное изменение позиции и масштаба
import { tween } from "popmotion";
tween({
from: { x: 0, scale: 1 },
to: { x: 400, scale: 2 },
duration: 500
}).start(v => {
element.style.transform = `
translateX(${v.x}px)
scale(${v.scale})
`;
});
Состояние остаётся единым, что исключает рассинхронизацию анимаций.
Переходы могут быть остановлены, перезапущены или изменены во время выполнения. Это важно для интерактивных интерфейсов.
const animation = tween({
from: 0,
to: 1000,
duration: 2000
}).start(v => {
slider.style.left = v + "px";
});
setTimeout(() => animation.stop(), 1000);
Состояние в этом случае фиксируется в момент остановки, и может быть использовано как новое начальное значение.
Одна из ключевых особенностей динамических систем — возможность менять конечное состояние во время анимации.
let current = 0;
const controls = tween({
from: current,
to: 500,
duration: 800
}).start(v => {
current = v;
box.style.transform = `translateX(${v}px)`;
});
// позже изменение цели
controls.stop();
tween({
from: current,
to: 100,
duration: 400
}).start(v => {
box.style.transform = `translateX(${v}px)`;
});
Такой подход позволяет строить адаптивные интерфейсы, реагирующие на действия пользователя без рывков.
Сложные системы часто комбинируют tween и spring-переходы.
Переход между состояниями становится гибридным процессом, где разные участки движения подчиняются разным законам.
Любая анимация может быть прервана, после чего текущее значение становится новым состоянием.
let x = 0;
const anim = tween({
from: 0,
to: 300,
duration: 1000
}).start(v => {
x = v;
});
Это создаёт непрерывную цепочку состояний, где каждый новый переход начинается с последнего известного значения.
В более сложных системах анимация рассматривается не как набор отдельных запусков, а как поток состояния:
Такой подход характерен для интерактивных UI, где каждое действие пользователя изменяет траекторию движения без перезапуска всей анимации.
При проектировании интерфейсов переходы часто организуются в иерархии:
Это позволяет избегать конфликтов анимаций и поддерживать согласованность визуального поведения.
Каждый уровень управляет своим состоянием и передаёт изменения ниже по иерархии, формируя единый поток преобразований.