Реактивность в Popmotion строится вокруг идеи потоков значений, которые изменяются во времени и могут быть преобразованы, скомбинированы и привязаны к побочным эффектам (например, обновлению DOM или запуску анимации). Вместо императивного управления каждым кадром система оперирует непрерывными значениями, формируя декларативную модель поведения.
Базовая единица — поток, который может представлять:
Каждый поток можно рассматривать как функцию времени
f(t) → value, где значение обновляется на каждом кадре.
Ключевой строительный блок — value. Он представляет
собой изменяемое реактивное значение, которое может:
import { value } from 'popmotion';
const x = value(0);
x.subscribe(v => {
console.log('x:', v);
});
x.update(100);
Значение становится точкой входа в реактивную систему: любое изменение распространяется дальше по цепочке подписчиков.
Внутри архитектуры value работает как
observable-источник, который может быть связан с анимационными
действиями и преобразованиями.
Механизм подписки позволяет строить реактивные цепочки:
const y = value(0);
y.subscribe(v => {
element.style.transform = `translateY(${v}px)`;
});
Каждое обновление значения немедленно отражается в побочном эффекте.
Такой подход устраняет необходимость ручного управления
requestAnimationFrame для базовых сценариев.
Подписка может рассматриваться как терминальная операция потока — точка, где реактивность превращается в действие.
В Popmotion анимации сами являются потоками значений. Основные типы:
tween — интерполяция между значениямиspring — физическая пружинаdecay — затухающее движениеКаждый из них возвращает поток, который эмитит значения по мере выполнения.
import { tween } from 'popmotion';
tween({
from: 0,
to: 300,
duration: 1000
}).start(v => {
element.style.transform = `translateX(${v}px)`;
});
Tween генерирует линейно интерполируемую последовательность значений, зависящую от времени, но не от состояния системы.
import { spring } from 'popmotion';
spring({
from: 0,
to: 300,
stiffness: 200,
damping: 20
}).start(v => {
element.style.transform = `translateX(${v}px)`;
});
Spring строится на решении дифференциального уравнения, где текущее состояние зависит от предыдущего. Это делает поток нелинейным и адаптивным.
Ключевая особенность — естественное затухание и инерция.
import { decay } from 'popmotion';
decay({
from: 0,
velocity: 500
}).start(v => {
element.style.transform = `translateX(${v}px)`;
});
Decay моделирует постепенное уменьшение скорости без фиксированной цели.
Реактивные значения можно трансформировать до попадания в побочные эффекты. Это позволяет отделять логику анимации от визуального слоя.
Типовые операции:
import { pipe, clamp, transform } from 'popmotion';
const process = pipe(
v => v * 2,
clamp(0, 300)
);
Поток значений превращается в конвейер обработки данных, где каждая функция — независимый этап трансформации.
Несколько потоков могут быть объединены в единый реактивный сигнал.
Пример синхронизации координат:
import { pointer, combine } from 'popmotion';
const mouse = pointer();
mouse.subscribe(({ x, y }) => {
element.style.transform = `translate(${x}px, ${y}px)`;
});
Pointer здесь выступает как поток событий, эмитирующий координаты в реальном времени.
Pointer преобразует события мыши или касания в непрерывный поток значений.
Характеристики:
import { pointer } from 'popmotion';
const track = pointer();
track.start(({ x, y }) => {
dot.style.left = x + 'px';
dot.style.top = y + 'px';
});
В отличие от DOM-событий, поток не требует ручного добавления и удаления слушателей.
Реактивная модель позволяет связывать пользовательский ввод и физические анимации без промежуточных состояний.
Пример: перетаскивание с инерцией
import { pointer, value, spring } from 'popmotion';
const x = value(0);
pointer()
.pipe(({ x: px }) => px)
.start(v => x.update(v));
x.subscribe(v => {
element.style.transform = `translateX(${v}px)`;
});
x.onComplete(() => {
spring({ from: x.get(), to: 0 }).start(v => x.update(v));
});
Здесь поток ввода напрямую управляет состоянием, а завершение взаимодействия запускает физическую модель.
Потоки могут быть выстроены в цепочки преобразований:
import { pointer, pipe } from 'popmotion';
const process = pipe(
({ x }) => x,
v => v / 2
);
pointer().pipe(process).start(v => {
element.style.transform = `translateX(${v}px)`;
});
Композиция устраняет необходимость промежуточных переменных и синхронизационных слоев.
Вся система синхронизирована с requestAnimationFrame.
Это означает:
Потоки не опрашиваются, а «проталкивают» значения при изменении состояния.
Каждый поток имеет управляемый цикл:
const animation = spring({ from: 0, to: 100 });
const controls = animation.start(v => {
box.style.transform = `translateX(${v}px)`;
});
controls.stop();
Управление потоком становится явным и изолированным от DOM.
Реактивный подход устраняет необходимость ручной синхронизации состояний:
Каждое значение в системе может быть источником и потребителем одновременно, формируя граф зависимостей, где изменения распространяются автоматически.
Физические модели становятся частными случаями потоков:
Все они унифицируются одной концепцией: поток значений во времени.
Такой подход позволяет строить интерфейсы, где поведение элементов определяется не отдельными анимациями, а связями между реактивными узлами.