В Popmotion базовая анимация почти всегда описывается как функция,
которая создаёт экземпляр движения: tween,
spring, physics, keyframes.
Прямое использование таких вызовов в коде приводит к дублированию логики
и усложняет поддержку. Основной подход к переиспользованию — вынесение
конфигурации и поведения в фабрики.
import { tween, easing } from "popmotion";
const fadeAnimation = (from = 0, to = 1, duration = 300) =>
tween({
from,
to,
duration,
ease: easing.easeInOut
});
fadeAnimation(0, 1).start({
update: v => console.log(v)
});
Фабрика не только параметризует анимацию, но и фиксирует семантику: «fadeAnimation» всегда означает изменение прозрачности, независимо от контекста использования.
Повторное использование становится устойчивым, когда логика анимации отделяется от момента запуска. Это особенно важно при работе с интерфейсами, где одно и то же движение используется в разных компонентах.
const createSlide = ({
fromX = -100,
toX = 0,
duration = 400
} = {}) => ({
from: { x: fromX },
to: { x: toX },
duration
});
Исполнение:
import { tween } from "popmotion";
const slide = createSlide();
tween(slide).start(v => {
element.style.transform = `translateX(${v.x}px)`;
});
Такой подход позволяет хранить «чистую спецификацию» анимации отдельно от механизма её применения.
Tween в Popmotion наиболее универсален, поэтому именно он чаще всего становится основой для шаблонов.
const presets = {
fast: {
duration: 200,
ease: "easeOut"
},
normal: {
duration: 400,
ease: "easeInOut"
},
slow: {
duration: 800,
ease: "easeIn"
}
};
const fadeIn = (preset = presets.normal) =>
tween({
from: 0,
to: 1,
...preset
});
Фактически формируется система дизайна анимаций, аналогичная design tokens в UI.
Popmotion позволяет комбинировать анимации через последовательный запуск или ручное управление временем. Переиспользование достигается не только повторным вызовом функций, но и сборкой сложных сценариев из простых блоков.
const fadeIn = () =>
tween({ from: 0, to: 1, duration: 300 });
const moveUp = () =>
tween({ from: { y: 20 }, to: { y: 0 }, duration: 300 });
Последовательная композиция:
fadeIn().start(v => console.log("fade", v));
moveUp().start(v => console.log("move", v));
Более строгая композиция через синхронизацию:
import { parallel } from "popmotion";
parallel(fadeIn(), moveUp()).start(v => {
console.log(v);
});
Каждая базовая анимация остаётся переиспользуемой единицей.
Physics-анимации особенно удобно инкапсулировать, так как они зависят от начальных условий.
import { physics } from "popmotion";
const createGravityDrop = (initialVelocity = 0) => ({
velocity: initialVelocity,
friction: 0.8,
restSpeed: 0.5
});
Использование:
physics(createGravityDrop(10)).start(v => {
element.style.transform = `translateY(${v}px)`;
});
Такая обёртка превращает физику в переиспользуемый модуль поведения, а не одноразовую конфигурацию.
Motion values часто используются как связующее звено между анимацией и DOM. Их можно сделать универсальными, если не привязывать к конкретному элементу.
import { value, tween } from "popmotion";
const opacity = value(0);
const fadeInValue = (mv) =>
tween({
from: 0,
to: 1,
duration: 300
}).start(v => mv.set(v));
Теперь одна и та же функция применима к любому motion value.
fadeInValue(opacity);
fadeInValue(anotherOpacity);
Easing часто становится скрытым источником дублирования. Выделение стратегий упрощает масштабирование системы анимаций.
import { easing } from "popmotion";
const easings = {
entrance: easing.easeOut,
exit: easing.easeIn,
emphasis: easing.backOut
};
const createScaleIn = (ease = easings.entrance) =>
tween({
from: 0.8,
to: 1,
duration: 250,
ease
});
Easing перестаёт быть параметром «на месте» и становится частью системы поведения.
Функции высшего порядка позволяют оборачивать любую анимацию и добавлять к ней поведение.
const withDelay = (animationFactory, delay = 0) => () =>
new Promise(resolve => {
setTimeout(() => {
animationFactory().start({
complete: resolve
});
}, delay);
});
Применение:
const delayedFade = withDelay(fadeIn, 200);
delayedFade().then(() => {
console.log("done");
});
Такой подход позволяет строить библиотеки анимаций, а не разрозненные функции.
Часто интерфейсные движения представляют собой фиксированные последовательности: появление → движение → фиксация состояния.
const sequence = (animations) => {
const run = (index = 0) => {
if (index >= animations.length) return;
animations[index]().start({
complete: () => run(index + 1)
});
};
run();
};
Использование:
sequence([
() => tween({ from: 0, to: 1, duration: 200 }),
() => tween({ from: 1, to: 1, duration: 200 }),
() => tween({ from: 1, to: 0, duration: 200 })
]);
Каждая последовательность становится переиспользуемым паттерном поведения.
При росте проекта набор анимаций начинает выполнять роль архитектурного слоя. Он отделяет продуктовую логику от деталей движения.
const animations = {
modal: {
open: () =>
tween({
from: { opacity: 0, scale: 0.9 },
to: { opacity: 1, scale: 1 },
duration: 250
}),
close: () =>
tween({
from: { opacity: 1, scale: 1 },
to: { opacity: 0, scale: 0.9 },
duration: 200
})
}
};
Использование:
animations.modal.open().start(v => {
element.style.opacity = v.opacity;
element.style.transform = `scale(${v.scale})`;
});
Такая структура делает анимации частью доменной модели интерфейса.
Чтобы переиспользование было масштабируемым, интерфейс запуска должен быть единым для всех типов анимаций.
const runAnimation = (animation) =>
animation.start({
update: v => console.log(v),
complete: () => console.log("done")
});
Теперь любые фабрики можно подключать без знания их внутренней реализации:
runAnimation(fadeIn());
runAnimation(moveUp());
runAnimation(animations.modal.open());
Единый контракт превращает разрозненные функции в систему.