Реактивность и анимации

Реактивность в Popmotion строится вокруг идеи потоков значений, которые изменяются во времени и могут быть преобразованы, скомбинированы и привязаны к побочным эффектам (например, обновлению DOM или запуску анимации). Вместо императивного управления каждым кадром система оперирует непрерывными значениями, формируя декларативную модель поведения.

Базовая единица — поток, который может представлять:

  • положение анимации во времени
  • значение физической модели (инерция, пружина)
  • координаты курсора
  • произвольное состояние UI

Каждый поток можно рассматривать как функцию времени f(t) → value, где значение обновляется на каждом кадре.


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 — затухающее движение

Каждый из них возвращает поток, который эмитит значения по мере выполнения.

Tween как детерминированный поток

import { tween } from 'popmotion';

tween({
  from: 0,
  to: 300,
  duration: 1000
}).start(v => {
  element.style.transform = `translateX(${v}px)`;
});

Tween генерирует линейно интерполируемую последовательность значений, зависящую от времени, но не от состояния системы.


Spring как физическая модель

import { spring } from 'popmotion';

spring({
  from: 0,
  to: 300,
  stiffness: 200,
  damping: 20
}).start(v => {
  element.style.transform = `translateX(${v}px)`;
});

Spring строится на решении дифференциального уравнения, где текущее состояние зависит от предыдущего. Это делает поток нелинейным и адаптивным.

Ключевая особенность — естественное затухание и инерция.


Decay как инерционный поток

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 как реактивный источник событий

Pointer преобразует события мыши или касания в непрерывный поток значений.

Характеристики:

  • непрерывность координат
  • поддержка multitouch
  • отсутствие ручного управления событиями
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. Это означает:

  • обновления происходят перед отрисовкой кадра
  • значения интерполируются плавно
  • минимизируется рассинхронизация UI

Потоки не опрашиваются, а «проталкивают» значения при изменении состояния.


Управление жизненным циклом потоков

Каждый поток имеет управляемый цикл:

  • старт
  • активная генерация значений
  • завершение
  • отмена
const animation = spring({ from: 0, to: 100 });

const controls = animation.start(v => {
  box.style.transform = `translateX(${v}px)`;
});

controls.stop();

Управление потоком становится явным и изолированным от DOM.


Реактивность и состояние интерфейса

Реактивный подход устраняет необходимость ручной синхронизации состояний:

  • нет отдельных моделей UI и анимации
  • состояние всегда производное от потоков
  • визуальный слой является функцией реактивных значений

Каждое значение в системе может быть источником и потребителем одновременно, формируя граф зависимостей, где изменения распространяются автоматически.


Связь физики и реактивной модели

Физические модели становятся частными случаями потоков:

  • spring — система с обратной связью
  • decay — система с затухающей энергией
  • tween — параметрическое время

Все они унифицируются одной концепцией: поток значений во времени.

Такой подход позволяет строить интерфейсы, где поведение элементов определяется не отдельными анимациями, а связями между реактивными узлами.