В Popmotion ключевая идея заключается в том, что любая анимация представляет собой функцию или композицию функций, а не одноразовую декларацию. Это позволяет выделять повторяющиеся части поведения в отдельные сущности и затем комбинировать их в разных контекстах.
Наиболее частая ошибка при работе с анимациями — дублирование конфигураций. Вместо этого используется подход с общими параметрами, которые передаются в фабрики.
import { animate } from "popmotion";
const baseSpring = {
type: "spring",
stiffness: 200,
damping: 20
};
animate({
from: 0,
to: 100,
...baseSpring
});
animate({
from: 200,
to: 300,
...baseSpring
});
Такой подход формирует основу для масштабирования: изменение одного параметра автоматически влияет на все анимации, использующие общий конфиг.
Следующий уровень переиспользования — фабрики, возвращающие готовые конфигурации или функции анимации. Это превращает Popmotion в систему конструкторов поведения.
import { animate } from "popmotion";
const createFadeAnimation = (from = 0, to = 1, duration = 300) => ({
from,
to,
duration,
ease: t => t
});
const fadeIn = (element) => {
animate({
...createFadeAnimation(0, 1, 400),
onUpdate: v => {
element.style.opacity = v;
}
});
};
Фабрика инкапсулирует знания о том, как должна выглядеть анимация появления, а конкретное применение отделяется от описания.
Popmotion построен вокруг идеи композиции. Любая трансформация может быть объединена с другой, создавая цепочки обработки значений.
Функция pipe позволяет соединять несколько трансформеров
в единый поток:
import { pipe, clamp, blendColor } from "popmotion";
const transformValue = pipe(
clamp(0, 100),
v => v / 100,
v => blendColor("#ff0000", "#0000ff", v)
);
Такой подход позволяет создавать переиспользуемые цепочки преобразований, которые не зависят от конкретной анимации.
В Popmotion часто требуется повторно использовать не сами анимации, а функции преобразования данных. Особенно это важно при работе с жестами, скроллом и физическими моделями.
const normalize = (min, max) => value =>
(value - min) / (max - min);
const scale = factor => value => value * factor;
const transform = pipe(
normalize(0, 500),
scale(100)
);
Эти функции становятся строительными блоками для любых интерактивных сценариев.
Физические модели в Popmotion, такие как spring, особенно хорошо поддаются переиспользованию через параметрические конфигурации.
import { animate } from "popmotion";
const createSpring = (stiffness = 180, damping = 12) => ({
type: "spring",
stiffness,
damping
});
const slowSpring = createSpring(80, 30);
const fastSpring = createSpring(300, 15);
animate({
from: 0,
to: 100,
...slowSpring
});
Параметризация позволяет описывать целые «семейства» анимаций, где изменяется только поведение физической модели, а структура остаётся одинаковой.
При масштабировании интерфейсов возникает необходимость стандартизировать поведение интерфейсных элементов. Пресеты позволяют фиксировать такие паттерны.
const presets = {
fade: {
duration: 300,
ease: t => t
},
slideUp: {
duration: 500,
ease: t => 1 - Math.pow(1 - t, 3)
}
};
const animateFade = (element) =>
animate({
from: 0,
to: 1,
...presets.fade,
onUpdate: v => element.style.opacity = v
});
Пресеты уменьшают когнитивную нагрузку при проектировании интерфейса, обеспечивая единый визуальный язык.
Функции высшего порядка позволяют создавать универсальные анимационные обёртки, которые можно применять к любым элементам.
const withOpacityAnimation = (animation) => (element) =>
animation({
onUpdate: v => {
element.style.opacity = v;
}
});
const fadeAnimation = (options) =>
animate({
from: 0,
to: 1,
...options
});
const fadeElement = withOpacityAnimation(fadeAnimation);
fadeElement(document.querySelector(".box"));
Такой подход отделяет логику анимации от её применения, создавая независимые слои системы.
Popmotion позволяет создавать унифицированные потоки значений, которые могут использоваться в разных сценариях: drag, scroll, physics.
import { pointer, animate } from "popmotion";
const bindDrag = (element) => {
pointer({ x: 0, y: 0 })
.start(v => {
element.style.transform = `translate(${v.x}px, ${v.y}px)`;
});
};
Если выделить обработчик движения в отдельную функцию, он становится переиспользуемым модулем:
const applyTranslation = element => v => {
element.style.transform = `translate(${v.x}px, ${v.y}px)`;
};
const bindDrag = (element) =>
pointer({ x: 0, y: 0 }).start(applyTranslation(element));
Easing — один из наиболее часто переиспользуемых компонентов. Вместо копирования формул создаются отдельные функции.
const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
const easeInOutQuad = t =>
t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
Далее они применяются в разных анимациях без изменения структуры кода:
animate({
from: 0,
to: 1,
duration: 500,
ease: easeOutCubic
});
Вместо создания сложных монолитных анимаций Popmotion позволяет собирать их из простых элементов.
import { tween, spring } from "popmotion";
const fadeIn = (element) =>
tween({
from: 0,
to: 1,
duration: 300,
onUpdate: v => (element.style.opacity = v)
});
const slideIn = (element) =>
spring({
from: -100,
to: 0,
stiffness: 200,
damping: 20,
onUpdate: v => (element.style.transform = `translateX(${v}px)`)
});
Далее такие функции легко комбинировать:
const showElement = (element) => {
fadeIn(element);
slideIn(element);
};
Переиспользование достигается за счёт независимости каждой анимации.
Одним из мощных способов переиспользования является создание адаптеров, которые преобразуют поток значений в изменения DOM.
const setStyle = (element, property) => value => {
element.style[property] = value;
};
const animateOpacity = (element) =>
animate({
from: 0,
to: 1,
duration: 400,
onUpdate: setStyle(element, "opacity")
});
Адаптер делает любую анимацию совместимой с любым элементом без изменения логики.
Сложные интерфейсы часто требуют нескольких уровней конфигурации: базовый слой, тематический слой и локальный слой.
const base = {
duration: 300,
ease: t => t
};
const theme = {
duration: 500
};
const local = {
from: 0,
to: 1
};
const config = {
...base,
...theme,
...local
};
Такой подход позволяет централизованно управлять поведением всей системы анимаций.
В Popmotion поток значений может быть отделён от его визуального представления. Это делает возможным повторное использование одного источника данных для разных эффектов.
import { pointer } from "popmotion";
const position = pointer({ x: 0, y: 0 });
const applyX = element => v => {
element.style.left = v.x + "px";
};
const applyY = element => v => {
element.style.top = v.y + "px";
};
const el1 = document.querySelector(".a");
const el2 = document.querySelector(".b");
position.start(applyX(el1));
position.start(applyY(el2));
Один поток управляет несколькими элементами без дублирования логики.
Переиспользование в Popmotion достигается не копированием кода, а выделением поведения в независимые единицы: функции, конфигурации, потоки и трансформеры. Эти единицы можно комбинировать без изменения внутренней структуры, формируя гибкую систему анимации, где каждый элемент остаётся самостоятельным и повторно применимым в других сценариях.