Библиотека построена вокруг принципа минимальных независимых блоков, каждый из которых решает строго ограниченную задачу: интерполяцию значений, управление временем, физические модели движения, преобразование данных. Вместо единого монолита используется набор функций, которые соединяются через композицию.
Такой подход влияет на всё: структуру импорта, размер бандла, тестируемость, расширяемость и предсказуемость поведения анимаций.
Каждая сущность библиотеки реализует одну конкретную ответственность:
Эти блоки не зависят от единого состояния анимационного движка. Они получают входные данные и возвращают поток значений.
Модульность выражается через функциональный стиль. Вместо классов используется набор чистых функций.
Пример композиции tween:
import { tween } from '@popmotion/popcorn';
import { easing } from '@popmotion/easing';
tween({
from: 0,
to: 100,
duration: 500,
ease: easing.easeOut
}).start(v => {
console.log(v);
});
Каждая часть легко заменяется:
Центральная концепция модульности — Action.
Action представляет собой поток значений, который можно:
Любая анимация — это Action.
const myAction = tween({
from: 0,
to: 1
});
Action не содержит UI, DOM-логики или состояния приложения. Это изолированная единица вычислений.
Одно из ключевых преимуществ модульной архитектуры — возможность построения цепочек преобразований.
import { pipe } from 'popmotion';
import { clamp, blendColor } from '@popmotion/popcorn';
const process = pipe(
clamp(0, 100),
v => v * 2,
Math.round
);
Каждая функция принимает значение и возвращает новое, не изменяя исходное.
Pipe делает возможной функциональную композицию без создания классовых иерархий.
Модуль transform реализует систему преобразования значений:
Пример:
import { mix } from '@popmotion/popcorn';
const blend = mix(0, 100);
blend(0.5); // 50
Каждая функция создаётся как фабрика, возвращающая специализированный трансформер.
Spring и decay — отдельные математические модели, не зависящие от tween.
import { spring } from '@popmotion/popcorn';
spring({
from: 0,
to: 100,
stiffness: 80,
damping: 10
}).start(console.log);
Физическая модель не требует таймлайна. Она вычисляет состояние на основе уравнений движения.
import { decay } from '@popmotion/popcorn';
decay({
from: 100,
velocity: 20
}).start(console.log);
Decay не имеет фиксированной длительности — поведение определяется физикой затухания.
Временная модель вынесена в отдельные модули. Tween не знает о DOM, requestAnimationFrame или UI.
Вместо этого используется абстракция времени:
Это позволяет тестировать анимации без браузера.
Модульная структура напрямую влияет на сборку.
Импортируется только необходимое:
import { spring } from '@popmotion/popcorn';
В итоговый бандл не попадают:
Каждый модуль является ES-модулем с явными экспортами.
Архитектура разделена на несколько пакетов:
Каждый пакет можно использовать отдельно.
Пример использования только transform:
import { pipe } from 'popmotion';
import { clamp } from '@popmotion/popcorn';
const safe = pipe(clamp(0, 1));
Отсутствует необходимость подключать всю систему анимаций.
Вместо классов и наследования применяется композиция функций.
Сложная анимация собирается из простых частей:
import { tween, spring } from '@popmotion/popcorn';
import { pipe } from 'popmotion';
const animation = spring({
from: 0,
to: 300
});
animation.start(v => {
const processed = pipe(Math.round, v => v / 2)(v);
console.log(processed);
});
Каждый слой добавляет новое поведение, не изменяя исходный модуль.
Архитектура делится на три уровня:
Каждый уровень не знает о внутренностях других.
Модули спроектированы так, чтобы их можно было использовать вне контекста анимации.
Пример: easing как чистая функция
import { easeInOut } from '@popmotion/easing';
easeInOut(0.3);
Без tween, без Action, без времени.
Компоненты взаимодействуют только через:
Отсутствуют:
Action можно рассматривать как поток данных:
источник → преобразование → подписчик
Каждый этап заменяем и расширяем.
Пример:
const stream = spring({ from: 0, to: 1 });
stream.start(value => {
console.log(value);
});
Поток не зависит от потребителя.
Рост проекта не приводит к усложнению ядра. Вместо этого добавляются новые независимые модули:
Ядро остаётся неизменным.
Нет единого “animation engine”. Есть набор независимых механизмов.
Это устраняет:
Каждый модуль:
Это позволяет точно воспроизводить анимации при одинаковых входных данных.
Добавление новой логики не требует изменения ядра.
Пример пользовательского модуля:
const double = v => v * 2;
const custom = tween({ from: 0, to: 1 });
custom.start(v => {
console.log(double(v));
});
Расширение происходит на уровне функций, а не архитектуры.