Архитектура Popmotion построена вокруг композиции функций и потоков значений, где любое поведение анимации представляется как action — независимый источник значений, который может быть преобразован, объединён и скомбинирован с другими action.
Расширение библиотеки в классическом смысле плагинов здесь заменено несколькими механизмами:
Ключевой принцип — отсутствие централизованного реестра плагинов. Вместо этого используется функциональная композиция.
Любая анимационная сущность в Popmotion реализуется как action —
объект с методом start, возвращающий поток значений.
Базовая структура action:
const customAction = () => ({
start: (upd ate) => {
upd ate(0)
upd ate(1)
upd ate(2)
return {
stop: () => {}
}
}
})
Такой подход позволяет воспринимать любой плагин как источник данных во времени.
Action поддерживает подключение через pipe, что делает
его совместимым с трансформациями.
Наиболее близкий аналог плагинов в Popmotion — pipable функции. Это функции высшего порядка, которые принимают поток значений и возвращают новый поток с модификацией.
Структура pipable функции:
const multiply = (factor) => (v) => v * factor
Использование:
import { pipe } from 'popmotion'
pipe(
multiply(2),
multiply(3)
)(value => console.log(value))
Фактически каждый такой трансформер является мини-плагином, внедряемым в поток данных.
Типовой сценарий расширения — создание преобразователя значений.
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, который управляет жизненным циклом значений.
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 может быть использован как часть композиции через
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)
В Popmotion существует механизм value, который управляет
состоянием и связывает actions с DOM.
Создание кастомного поведения для value:
import { value } from 'popmotion'
const x = value(0, (v) => {
console.log('updated:', v)
})
Такой объект может быть расширен через собственные pipable функции, превращая его в точку интеграции плагинов.
Для работы с DOM используется styler — обёртка над стилями, позволяющая анимировать свойства напрямую.
import { styler } from 'popmotion'
const box = styler(document.querySelector('.box'))
box.se t({
x: 100,
opacity: 0.5
})
Расширение 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.
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))
}
Такой модуль объединяет:
tween)Любое расширение Popmotion опирается на несколько устойчивых принципов:
Функциональная чистота Преобразования не должны зависеть от глобального состояния.
Композиционность Любой элемент должен быть совместим с pipe-цепочкой.
Изолированность побочных эффектов DOM и side-effects выносятся на границы системы.
Повторное использование Каждый transform или action может применяться в разных контекстах.
Наиболее гибкий способ создания расширений — фабрики функций.
const createSpringPlugin = (config) => {
return (v) =>
spring({
from: v,
...config
})
}
Такая конструкция позволяет параметризовать поведение и использовать его как модуль.
Сложные расширения объединяют несколько уровней:
Пример:
const parallaxPlugin = (element) => {
const s = styler(element)
return pipe(
smooth(0.2),
(v) => v * 0.5
)((v) => s.se t({ y: v }))
}
Такой подход формирует расширение, которое интегрируется в поток значений без изменения архитектуры ядра.