API расширений

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

В основе лежит контракт действия: объект, который можно запустить, подписаться на его значения и остановить. Любое расширение Popmotion фактически создаёт новый тип действия, совместимый с системой исполнения.

Ключевые элементы:

  • функция запуска (start)
  • механизм подписки (observer)
  • функция остановки (stop)
  • поток значений (values over time)

Любая кастомизация API сводится к созданию новой реализации этого контракта или модификации существующих потоков.

Создание пользовательских действий

Popmotion предоставляет фабричный подход для создания собственных actions. Это позволяет интегрировать любую логику: от сетевых событий до сложных вычислительных моделей.

Типовой паттерн построения:

import { action } from 'popmotion';

const customAction = (config) => {
  return action(({ update, complete }) => {
    let count = 0;

    const interval = setInterval(() => {
      count += 1;
      upd ate(count);

      if (count >= config.max) {
        clearInterval(interval);
        complete();
      }
    }, config.delay);

    return {
      stop: () => clearInterval(interval)
    };
  });
};

Такая структура демонстрирует фундаментальный принцип: любое поведение превращается в поток обновлений.

Расширение через композицию потоков

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

Основные операторы композиции:

  • pipe — последовательное преобразование значений
  • map — трансформация каждого значения
  • filter — фильтрация потока
  • merge — объединение нескольких потоков

Пример композиции:

import { pipe, transform } from 'popmotion';

const scaleAndClamp = pipe(
  transform.map(v => v * 2),
  transform.clamp(0, 100)
);

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

Расширение через трансформеры

Transformers представляют собой чистые функции, изменяющие поток данных без побочных эффектов. Они служат базовым механизмом адаптации значений между различными действиями.

Основные виды трансформаций:

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

Пример пользовательского трансформера:

const invert = v => 1 - v;

const normalizeRange = (min, max) => v =>
  (v - min) / (max - min);

Такие функции легко подключаются к любому action, создавая гибкую систему адаптации данных.

Создание расширяемых easing-функций

Easing в Popmotion рассматривается как функция преобразования прогресса анимации. Расширение easing API заключается в создании собственных математических моделей поведения времени.

Стандартная форма:

const customEase = t => t * t * (3 - 2 * t);

Расширенные варианты используют составные функции:

const elasticEase = t => {
  const p = 0.3;
  return Math.pow(2, -10 * t) *
    Math.sin((t - p / 4) * (2 * Math.PI) / p) + 1;
};

Easing-функции легко комбинируются через композицию, формируя новые временные кривые без изменения ядра библиотеки.

Расширение физических моделей

Popmotion включает физические модели (spring, decay), которые также могут быть модифицированы или заменены.

Расширение spring-модели обычно происходит через изменение параметров жесткости, демпфирования и массы:

spring({
  stiffness: 200,
  damping: 20,
  mass: 1.5
});

Построение собственной физической модели возможно через реализацию интеграционного шага:

const customPhysics = ({ velocity, friction }) =>
  action(({ update, complete }) => {
    let v = velocity;
    let x = 0;

    const step = () => {
      v *= friction;
      x += v;

      update(x);

      if (Math.abs(v) < 0.01) complete();
      else requestAnimationFrame(step);
    };

    step();
  });

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

Интеграция пользовательских источников значений

Одним из ключевых механизмов расширения является подключение внешних источников данных как потоков. Любое событие может быть преобразовано в action.

Примеры источников:

  • DOM-события
  • WebSocket сообщения
  • таймеры
  • сенсоры устройства
  • вычислительные процессы

Пример интеграции события мыши:

const mouseMoveAction = () =>
  action(({ update }) => {
    const handler = e => update({ x: e.clientX, y: e.clientY });

    window.addEventListener('mousemove', handler);

    return {
      stop: () => window.removeEventListener('mousemove', handler)
    };
  });

Таким образом API расширяется за счёт превращения внешнего мира в поток значений.

Создание цепочек преобразований действий

Actions в Popmotion могут быть преобразованы в другие actions. Это создаёт слой мета-расширения, где поведение одного действия модифицируется другим.

Пример обёртки:

const withThreshold = (threshold, actionFactory) =>
  (...args) =>
    action(({ update, complete }) => {
      let started = false;

      const child = actionFactory(...args);

      const subscription = child.start({
        update: v => {
          if (v > threshold) started = true;
          if (started) update(v);
        },
        complete
      });

      return {
        stop: subscription.stop
      };
    });

Такой подход позволяет строить middleware-подобные системы поверх базовых действий.

Паттерн расширения через фабрики

Фабрики действий являются основным способом масштабирования API без изменения ядра. Они позволяют инкапсулировать сложную логику создания потоков.

Типовая структура фабрики:

const createSequencedAction = actions =>
  action(({ update, complete }) => {
    let index = 0;

    const runNext = () => {
      if (index >= actions.length) {
        complete();
        return;
      }

      actions[index++].start({
        update,
        complete: runNext
      });
    };

    runNext();
  });

Фабрики формируют уровень абстракции, позволяющий собирать сложные анимационные сценарии из простых блоков.

Расширение системы значений (value layer)

Value layer отвечает за хранение и распространение текущего состояния анимации. Расширение этого слоя позволяет подключать реактивные модели обновления.

Типовые расширения:

  • синхронизация нескольких value
  • зеркалирование состояний
  • двусторонние биндинги
  • кэширование последнего состояния

Пример синхронизации:

const syncValues = (a, b) =>
  a.subscribe(v => b.se t(v));

Эта модель превращает Popmotion в основу для реактивных систем управления состоянием.

Переопределение поведения через обёртки

Расширение API часто реализуется через wrapping существующих actions. Обёртка позволяет модифицировать поток без изменения исходной логики.

Пример логирования:

const withLogger = actionFactory =>
  (...args) =>
    action(({ update, complete }) => {
      const child = actionFactory(...args);

      return child.start({
        update: v => {
          console.log(v);
          update(v);
        },
        complete
      });
    });

Такой подход создаёт слой наблюдаемости над любым действием.

Модульная структура расширений

Расширения Popmotion логически группируются в независимые модули:

  • генераторы значений
  • трансформеры потоков
  • физические модели
  • источники событий
  • композиционные операторы

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

Интеграция расширений в единый поток исполнения

Все расширения Popmotion объединяются общей моделью исполнения: поток значений проходит через цепочку трансформаций, поступает в подписчиков и управляет внешними эффектами.

Общая схема расширяемого API сводится к трём уровням:

  • генерация значений
  • преобразование значений
  • потребление значений

Любое расширение попадает в один из этих уровней, сохраняя совместимость с системой без необходимости изменения ядра библиотеки.