В контексте анимаций под «хуками» понимаются функции или механизмы, позволяющие встраивать анимационное поведение в жизненный цикл приложения или компонентов. В библиотеке Popmotion хуки не представлены в виде отдельного API (как, например, в React), однако сама архитектура библиотеки предполагает функциональный, композиционный подход, который идеально сочетается с концепцией хуков.
Хуки в данном случае — это способ организовать переиспользуемую логику анимации: управление состоянием, подписки на обновления, синхронизация значений и реакции на изменения.
Ключевым элементом Popmotion является value. Он
выступает в роли контейнера состояния и источника реактивности.
import { value } from "popmotion";
const x = value(0, v => {
console.log(v);
});
x.update(100);
Особенности:
Таким образом, value играет роль аналога хука
состояния.
Подписка на изменения значения реализует поведение, аналогичное эффектам:
const unsubscribe = x.subscribe(v => {
console.log("Новое значение:", v);
});
Ключевые аспекты:
unsubscribeПример применения:
const element = document.querySelector(".box");
x.subscribe(v => {
element.style.transform = `translateX(${v}px)`;
});
Popmotion поощряет создание композиционных функций, которые можно рассматривать как кастомные хуки.
Пример:
import { tween } from "popmotion";
function useFadeIn(duration = 300) {
return tween({
from: { opacity: 0 },
to: { opacity: 1 },
duration
});
}
Использование:
const fade = useFadeIn(500);
fade.start({
update: v => {
element.style.opacity = v.opacity;
}
});
Преимущества:
pipe позволяет строить цепочки преобразований — аналог
вычисляемых значений (derived state):
import { pipe } from "popmotion";
import { clamp } from "popmotion";
const transform = pipe(
v => v * 2,
clamp(0, 100)
);
x.subscribe(v => {
console.log(transform(v));
});
Такой подход напоминает хуки вроде useMemo, где значение
вычисляется на основе других данных.
Popmotion предоставляет listen для работы с
событиями:
import { listen } from "popmotion";
const click$ = listen(document, "click");
const unsubscribe = click$.start(event => {
console.log(event.clientX);
});
Это можно рассматривать как хук для подписки на события.
Комбинирование с анимацией:
click$.start(event => {
tween({
from: 0,
to: event.clientX,
duration: 300
}).start(x);
});
Создание кастомных хуков для физически реалистичных анимаций:
import { spring } from "popmotion";
function useSpringValue(initial) {
const val = value(initial);
return {
value: val,
set: newValue => {
spring({
from: val.get(),
to: newValue
}).start(val);
}
};
}
Использование:
const { value: pos, set } = useSpringValue(0);
pos.subscribe(v => {
element.style.transform = `translateX(${v}px)`;
});
set(300);
Каждая анимация в Popmotion возвращает контроллер:
const animation = tween({
from: 0,
to: 100
}).start(v => {
console.log(v);
});
Остановка:
animation.stop();
Это позволяет реализовать поведение, аналогичное cleanup-функциям в хуках.
Можно строить сложные хуки, объединяя несколько
value:
const x = value(0);
const y = value(0);
function useCombined() {
return {
subscribe: callback => {
return x.subscribe(vx => {
callback({ x: vx, y: y.get() });
});
}
};
}
Popmotion предоставляет transform, позволяющий создавать
реактивные преобразования:
import { transform } from "popmotion";
const mapRange = transform({
inputRange: [0, 100],
outputRange: [0, 1]
});
x.subscribe(v => {
console.log(mapRange(v));
});
Анимации можно оборачивать в Promise:
function animateTo(value, to) {
return new Promise(resolve => {
tween({ from: value.get(), to })
.start({
update: v => value.update(v),
complete: resolve
});
});
}
Это позволяет строить последовательные сценарии:
await animateTo(x, 100);
await animateTo(x, 0);
Реализация управляемых хуков:
function useControlledValue(initial) {
let internal = value(initial);
return {
get: () => internal.get(),
set: v => internal.update(v),
subscribe: fn => internal.subscribe(fn)
};
}
Часто хуки используются для связывания логики и интерфейса:
function useDrag(element) {
const x = value(0);
listen(element, "mousedown").start(startEvent => {
listen(document, "mousemove").start(moveEvent => {
x.update(moveEvent.clientX - startEvent.clientX);
});
});
return x;
}
valueФункциональные хуки
Композиция
Изоляция логики
Реактивность
function useAnimatedOpacity(element) {
const opacity = value(0);
opacity.subscribe(v => {
element.style.opacity = v;
});
return {
fadeIn: () =>
tween({ from: 0, to: 1 }).start(opacity),
fadeOut: () =>
tween({ from: 1, to: 0 }).start(opacity)
};
}
function withLogging(animation) {
return {
start: props =>
animation.start({
...props,
update: v => {
console.log(v);
props.update && props.update(v);
}
})
};
}
Popmotion-хуки можно связывать с:
Пример:
store.subscribe(() => {
const state = store.getState();
x.update(state.position);
});
value как источник истиныБлагодаря функциональной природе Popmotion, хуки легко масштабируются:
Хуки в Popmotion — это не встроенный механизм, а архитектурный подход: