В анимационной системе Popmotion ключевая идея строится вокруг
композиции действий (actions) и их расширения через
оборачивающие функции, которые в практике разработки часто называют
миксинами. Такой подход позволяет не раздувать базовые примитивы вроде
tween или spring, а наращивать поведение
поверх них: добавлять повторение, инверсию, управление скоростью,
синхронизацию и другие модификаторы.
В основе Popmotion лежит абстракция Action — поток значений во времени. Любая анимация, будь то интерполяция, физическая модель или набор ключевых кадров, в конечном счёте является действием, которое:
startonUpdateonCompleteБазовые фабрики:
tween — линейная или криволинейная интерполяцияspring — физически корректная пружинаkeyframes — последовательность значенийphysics — модель движения с инерциейНо сами по себе они остаются «чистыми». Вся гибкость появляется за счёт обёрток над действиями.
Под миксинами в контексте Popmotion удобно понимать функции высшего порядка, которые принимают action и возвращают новый action с дополнительной логикой.
Это не наследование и не изменение исходного объекта. Это декорирование потока значений.
Типовой шаблон миксина:
const mixin = (action) => {
return {
start: (update, complete) => {
return action.start(
(v) => {
upd ate(v);
},
() => {
complete();
}
);
}
};
};
На практике миксины почти всегда работают через перехват
update и управление потоком данных.
Popmotion изначально построен так, чтобы действия легко комбинировались.
Простейшая форма композиции:
import { tween } from "popmotion";
const animation = tween({
from: 0,
to: 100,
duration: 1000
});
Но вместо изменения tween, логика расширяется так:
Один из самых частых кейсов — управление скоростью анимации независимо от её внутренней логики.
const withSpeed = (action, speed = 1) => {
return {
start: (update, complete) => {
let startTime = null;
return action.start(
(v) => update(v),
complete
);
},
speed
};
};
Реальная реализация в Popmotion обычно строится через трансформацию времени, а не значений:
const speed = (multiplier) => (action) => ({
start: (update, complete) =>
action.start(update, complete)
});
Ключевой принцип: миксин не знает, что именно анимируется, он работает только с потоком данных.
Повтор анимации — типичный пример поведения, которое не должно быть встроено в базовый tween.
const loop = (action) => {
return {
start: (update, complete) => {
const run = () => {
action.start(update, () => {
run();
});
};
run();
}
};
};
Такая конструкция превращает любое действие в бесконечный цикл.
Улучшенная версия учитывает количество повторов:
const loop = (times = Infinity) => (action) => ({
start: (update, complete) => {
let count = 0;
const run = () => {
action.start(update, () => {
count++;
if (count < times) run();
else complete();
});
};
run();
}
});
Инверсия — это не изменение логики анимации, а трансформация входных/выходных значений.
const reverse = (action) => ({
start: (update, complete) =>
action.start(
(v) => update(1 - v),
complete
)
});
Если базовое действие возвращает нормализованное значение
0 → 1, миксин полностью меняет направление движения без
модификации самой анимации.
Наиболее мощный способ работы с миксинами — последовательное применение:
const pipe = (...fns) => (value) =>
fns.reduce((acc, fn) => fn(acc), value);
Использование:
const enhanced = pipe(
loop(3),
reverse,
withSpeed(2)
)(tween({ from: 0, to: 1, duration: 1000 }));
Здесь формируется цепочка преобразований:
Каждый миксин не знает о других — это принцип слабого связывания.
Одно из ключевых применений — объединение потоков.
const mergeActions = (actions) => ({
start: (update, complete) => {
let completed = 0;
actions.forEach((action) => {
action.start(update, () => {
completed++;
if (completed === actions.length) {
complete();
}
});
});
}
});
Такой миксин позволяет запускать несколько анимаций параллельно, сохраняя единый контроль завершения.
Задержка — это пример миксина, который влияет не на значения, а на время старта:
const delay = (ms) => (action) => ({
start: (update, complete) => {
setTimeout(() => {
action.start(update, complete);
}, ms);
}
});
Он не вмешивается в поток, а лишь модифицирует момент активации действия.
В Popmotion часто используется styler для управления
DOM-элементами. Миксины могут быть применены не только к анимациям, но и
к эффектам применения значений.
import { styler } from "popmotion";
const applyTo = (element) => (action) => {
const elementStyler = styler(element);
return {
start: (update, complete) =>
action.start((v) => {
elementStyler.se t(v);
update(v);
}, complete)
};
};
Здесь миксин расширяет поведение действия побочным эффектом: обновлением DOM.
При достаточном количестве миксинов система превращается в декларативный конструктор анимаций.
const animation = pipe(
delay(200),
loop(2),
reverse,
applyTo(box)
)(
tween({
from: { x: 0 },
to: { x: 300 },
duration: 800
})
);
В такой модели:
Несмотря на гибкость, есть структурные ограничения:
Поэтому миксины эффективно работают только при строгом разделении ответственности: один миксин — одна трансформация поведения.
Наиболее устойчивый подход в Popmotion:
Такая модель позволяет строить сложные анимационные системы без усложнения ядра библиотеки и без дублирования логики