Создание плагинов

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

Расширение библиотеки в классическом смысле плагинов здесь заменено несколькими механизмами:

  • создание собственных actions
  • добавление pipable transformations
  • расширение через composable utilities
  • работа с value pipelines
  • обёртки над DOM-стилями через styler

Ключевой принцип — отсутствие централизованного реестра плагинов. Вместо этого используется функциональная композиция.


Action как базовая единица расширения

Любая анимационная сущность в Popmotion реализуется как action — объект с методом start, возвращающий поток значений.

Базовая структура action:

const customAction = () => ({
  start: (upd ate) => {
    upd ate(0)
    upd ate(1)
    upd ate(2)

    return {
      stop: () => {}
    }
  }
})

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

Action поддерживает подключение через pipe, что делает его совместимым с трансформациями.


Pipable-функции как форма плагинов

Наиболее близкий аналог плагинов в Popmotion — pipable функции. Это функции высшего порядка, которые принимают поток значений и возвращают новый поток с модификацией.

Структура pipable функции:

const multiply = (factor) => (v) => v * factor

Использование:

import { pipe } from 'popmotion'

pipe(
  multiply(2),
  multiply(3)
)(value => console.log(value))

Фактически каждый такой трансформер является мини-плагином, внедряемым в поток данных.


Создание собственного pipable трансформера

Типовой сценарий расширения — создание преобразователя значений.

Пример нормализации диапазона

const normalize = (min, max) => (v) =>
  (v - min) / (max - min)

Пример сглаживания значений

const smooth = (strength = 0.1) => {
  let prev = 0

  return (v) => {
    prev = prev + (v - prev) * strength
    return prev
  }
}

Такой трансформер может использоваться в любом pipeline без привязки к DOM или конкретной анимации.


Композиция как основа расширений

Popmotion строится на глубокой композиции функций. Любой “плагин” становится частью цепочки обработки.

pipe(
  normalize(0, 100),
  smooth(0.2),
  multiply(10)
)

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


Создание кастомного Action

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

Пример генератора временной анимации

import { action } from 'popmotion'

const counter = (from = 0, to = 100, duration = 1000) =>
  action(({ update, complete }) => {
    const start = performance.now()

    const tick = () => {
      const now = performance.now()
      const progress = Math.min((now - start) / duration, 1)

      const value = from + (to - from) * progress

      update(value)

      if (progress < 1) {
        requestAnimationFrame(tick)
      } else {
        complete()
      }
    }

    tick()

    return {
      stop: () => {}
    }
  })

Такой action является полноценным расширением системы анимации и может использоваться как базовый строительный блок.


Подключение кастомных action в pipeline

Любой action может быть использован как часть композиции через pipe или transform.

counter(0, 200, 2000)
  .start((v) => console.log(v))

При необходимости action может быть дополнен трансформациями:

counter(0, 100, 1000)
  .pipe(
    smooth(0.3),
    multiply(2)
  )
  .start(console.log)

Работа с value pipeline

В Popmotion существует механизм value, который управляет состоянием и связывает actions с DOM.

Создание кастомного поведения для value:

import { value } from 'popmotion'

const x = value(0, (v) => {
  console.log('updated:', v)
})

Такой объект может быть расширен через собственные pipable функции, превращая его в точку интеграции плагинов.


Расширение DOM-логики через styler

Для работы с DOM используется styler — обёртка над стилями, позволяющая анимировать свойства напрямую.

import { styler } from 'popmotion'

const box = styler(document.querySelector('.box'))

box.se t({
  x: 100,
  opacity: 0.5
})

Расширение styler осуществляется через добавление собственных свойств и трансформаций.


Создание plugin-like утилит для styler

Типовой способ расширения — создание функций, которые инкапсулируют сложную логику установки стилей.

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

const scaleFromCenter = (element, scale) => {
  const s = styler(element)

  s.se t({
    scale,
    originX: 0.5,
    originY: 0.5
  })
}

Такие утилиты фактически выполняют роль плагинов для DOM-слоя Popmotion.


Интеграция физических моделей как расширений

Popmotion поддерживает физические модели (spring, inertia), которые могут использоваться как плагины поведения.

Пример обёртки над инерцией:

import { inertia } from 'popmotion'

const momentumScroll = (from, velocity) =>
  inertia({
    from,
    velocity,
    power: 0.8,
    timeConstant: 325,
    restDelta: 0.5
  })

Такие конструкции расширяют поведение без изменения ядра системы.


Создание модульных анимационных расширений

Расширения Popmotion часто оформляются как чистые модули, объединяющие actions и transforms.

Пример модуля “fade + slide”

const fadeSlide = (element, x = 50) => {
  const s = styler(element)

  return tween({
    from: { opacity: 0, x },
    to: { opacity: 1, x: 0 },
    duration: 500
  }).start(v => s.se t(v))
}

Такой модуль объединяет:

  • action (tween)
  • styler
  • композицию значений

Принципы проектирования расширений

Любое расширение Popmotion опирается на несколько устойчивых принципов:

Функциональная чистота Преобразования не должны зависеть от глобального состояния.

Композиционность Любой элемент должен быть совместим с pipe-цепочкой.

Изолированность побочных эффектов DOM и side-effects выносятся на границы системы.

Повторное использование Каждый transform или action может применяться в разных контекстах.


Абстракция плагина через фабрики

Наиболее гибкий способ создания расширений — фабрики функций.

const createSpringPlugin = (config) => {
  return (v) =>
    spring({
      from: v,
      ...config
    })
}

Такая конструкция позволяет параметризовать поведение и использовать его как модуль.


Сборка комплексных расширений

Сложные расширения объединяют несколько уровней:

  • action (источник значений)
  • transform (обработка)
  • side-effects (DOM или события)
  • композиция pipeline

Пример:

const parallaxPlugin = (element) => {
  const s = styler(element)

  return pipe(
    smooth(0.2),
    (v) => v * 0.5
  )((v) => s.se t({ y: v }))
}

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