Popmotion строится вокруг идеи непрерывных потоков значений во времени, где анимация и взаимодействие представлены не как разовые процедуры, а как реактивные последовательности данных. Любое движение, жест или физическая симуляция в этой модели становится источником значений, которые можно трансформировать, комбинировать и направлять в другие источники поведения интерфейса.
Ключевой принцип — время не управляется напрямую, а наблюдается через поток значений. Это сближает архитектуру Popmotion с реактивным программированием, где данные текут, а не запрашиваются.
Основной строительный блок реактивной системы —
action.
action представляет собой поток, который:
Каждый action можно рассматривать как функцию, исполняемую с течением времени, а не мгновенно.
import { action } from 'popmotion';
const myStream = action(({ update }) => {
let x = 0;
const interval = setInterval(() => {
x += 1;
update(x);
}, 100);
return () => clearInterval(interval);
});
myStream.start({
update: v => console.log(v)
});
Внутри этого механизма:
update эмитит новое значениеstart активирует потокТаким образом, action становится универсальным
интерфейсом для любых потоков данных.
В Popmotion почти всё, что связано с движением, является источником реактивного потока.
tween генерирует значения на основе интерполяции:
import { tween } from 'popmotion';
const animation = tween({
from: 0,
to: 100,
duration: 1000
});
animation.start({
update: v => console.log(v)
});
Каждое значение — это сэмпл функции времени.
spring моделирует физическую систему:
Поток значений здесь не детерминирован по шагам времени, а определяется уравнением движения.
import { spring } from 'popmotion';
spring({
from: 0,
to: 300,
stiffness: 200
}).start({
update: v => console.log(v)
});
Это поток, где каждое новое значение зависит от предыдущего состояния системы.
decay создаёт поток затухающего движения:
import { decay } from 'popmotion';
decay({
velocity: 500
}).start({
update: v => console.log(v)
});
Здесь поток моделирует инерцию без конечной точки.
Потоки могут формироваться из пользовательского ввода:
import { pointer } from 'popmotion';
pointer().start({
update: ({ x, y }) => {
console.log(x, y);
}
});
Каждое событие ввода становится частью реактивного потока координат.
value — это особый поток, который хранит текущее
состояние и может быть изменён извне.
import { value } from 'popmotion';
const x = value(0);
x.update(10);
x.subscribe(v => console.log(v));
Он одновременно:
Любой поток в Popmotion имеет жизненный цикл:
Подписка осуществляется через start.
const stream = tween({ from: 0, to: 1 });
const controls = stream.start({
update: v => console.log(v),
complete: () => console.log('done')
});
controls.stop();
controls предоставляет управление жизненным циклом:
stop() — остановка потокаpause() — приостановка (в некоторых реализациях)resume() — возобновлениеРеактивная модель становится мощной благодаря преобразованиям данных.
pipe позволяет последовательно применять функции
трансформации.
import { pipe, clamp } from 'popmotion';
const transform = pipe(
v => v * 2,
clamp(0, 100)
);
Каждое значение проходит через цепочку операторов.
Функции преобразования часто используются для:
const double = v => v * 2;
В реактивной модели это не разовая операция, а трансформация потока.
Ограничение диапазона значений:
import { clamp } from 'popmotion';
const safe = clamp(0, 1);
Позволяет преобразовать числовой поток в другой диапазон или структуру.
const output = v => v / 100;
Реактивная архитектура раскрывается при объединении потоков.
Объединение нескольких источников в один поток.
import { merge, pointer, mouse } from 'popmotion';
const combined = merge(pointer(), mouse());
Здесь оба источника становятся единым потоком событий.
Последовательное выполнение потоков:
import { chain, tween, spring } from 'popmotion';
chain(
tween({ from: 0, to: 100 }),
spring({ from: 100, to: 0 })
);
Потоки можно вкладывать друг в друга:
Типичная структура реактивного поведения:
import { pointer, value, pipe, clamp } from 'popmotion';
const position = value({ x: 0, y: 0 });
const transform = pipe(
({ x, y }) => ({
x: clamp(0, 300)(x),
y: clamp(0, 500)(y)
})
);
pointer().start({
update: v => {
const next = transform(v);
position.update(next);
}
});
Здесь поток ввода преобразуется в поток состояния UI.
value выступает центральным узлом управления
состоянием.
Особенности:
const opacity = value(1);
opacity.subscribe(v => {
element.style.opacity = v;
});
При этом value может быть источником и приёмником
одновременно, что позволяет строить двунаправленные потоки.
Каждое CSS-свойство становится реактивной величиной:
const x = value(0);
x.subscribe(v => {
element.style.transform = `translateX(${v}px)`;
});
Поток ввода напрямую управляет физической моделью:
pointer().start({
update: ({ x }) => {
spring({
from: x,
to: 0
}).start({
update: v => element.style.left = v + 'px'
});
}
});
Вместо императивного вызова функций:
Реактивные потоки требуют внимания к следующим аспектам:
Отсутствие остановки потока приводит к:
Некоторые источники генерируют значения:
Необходима фильтрация или throttling.
Физические потоки (spring, decay) зависят
от:
что влияет на воспроизводимость поведения.
При большом числе потоков:
Потоки данных формируют структуру, в которой:
В рамках Popmotion это позволяет объединить:
в единую реактивную систему, где любое изменение входного сигнала мгновенно распространяется по графу потоков данных.