Синхронизация анимаций с внешними источниками — ключевой аспект при создании динамических интерфейсов. Библиотека Popmotion предоставляет инструменты для интеграции анимационных потоков с событиями пользователя, сетевыми запросами, состоянием приложения и другими асинхронными источниками.
В основе Popmotion лежит концепция потоков значений (streams). Любая анимация или событие представляется как последовательность значений во времени. Это позволяет объединять различные источники данных:
Пример создания базового потока:
import { value, tween } from "popmotion";
const x = value(0, v => console.log(v));
tween({ to: 100, duration: 1000 }).start(x);
Здесь value выступает как подписчик, который может быть
связан с внешними источниками.
Popmotion позволяет напрямую связывать анимации с пользовательскими
действиями через модуль listen.
import { listen } from "popmotion";
listen(document, "mousemove").start(event => {
console.log(event.clientX, event.clientY);
});
Объединение с анимацией:
import { listen, value } from "popmotion";
const cursorX = value(0, v => {
element.style.transform = `translateX(${v}px)`;
});
listen(document, "mousemove").start(event => {
cursorX.update(event.clientX);
});
Таким образом, анимация становится полностью зависимой от внешнего ввода.
Асинхронные данные, получаемые с сервера, могут управлять параметрами анимации.
fetch("/api/data")
.then(res => res.json())
.then(data => {
tween({
to: data.value,
duration: 500
}).start(v => {
element.style.opacity = v;
});
});
В этом случае:
Объект value играет роль промежуточного хранилища
состояния, которое может обновляться из разных источников.
const opacity = value(0, v => {
element.style.opacity = v;
});
// Обновление из API
opacity.update(0.5);
// Обновление из пользовательского события
listen(button, "click").start(() => {
opacity.update(1);
});
Это позволяет централизовать управление значениями и избегать конфликтов между источниками.
Popmotion поддерживает работу с временными источниками через
every-frame и стандартные таймеры.
import { everyFrame } from "popmotion";
everyFrame().start(delta => {
console.log("Прошло времени:", delta);
});
Пример синхронизации с setInterval:
setInterval(() => {
tween({ to: Math.random() * 100 }).start(v => {
element.style.width = `${v}px`;
});
}, 2000);
Для сложных сценариев требуется объединение нескольких источников. Popmotion позволяет это делать через композицию.
import { value, spring } from "popmotion";
const position = value(0, v => {
element.style.transform = `translateX(${v}px)`;
});
listen(document, "mousemove").start(e => {
spring({
from: position.get(),
to: e.clientX,
stiffness: 200
}).start(position);
});
Здесь:
При использовании глобального состояния (например, Redux), Popmotion может реагировать на изменения состояния.
store.subscribe(() => {
const state = store.getState();
tween({
to: state.position,
duration: 300
}).start(v => {
element.style.transform = `translateX(${v}px)`;
});
});
Это обеспечивает:
При наличии нескольких источников важно управлять приоритетами:
Пример отмены:
let currentAnimation;
function animateTo(value) {
if (currentAnimation) currentAnimation.stop();
currentAnimation = tween({ to: value }).start(v => {
element.style.opacity = v;
});
}
Для оптимизации частых событий (например, scroll или mousemove):
let timeout;
listen(window, "scroll").start(() => {
clearTimeout(timeout);
timeout = setTimeout(() => {
console.log("Скролл завершён");
}, 100);
});
Синхронизация возможна и с окружением браузера:
const mediaQuery = window.matchMedia("(max-width: 600px)");
mediaQuery.addEventListener("change", e => {
tween({
to: e.matches ? 100 : 300
}).start(v => {
element.style.width = `${v}px`;
});
});
Popmotion может использоваться для визуализации потоковых данных:
const socket = new WebSocket("wss://example.com");
socket.onmess age = event => {
const data = JSON.parse(event.data);
tween({
to: data.value,
duration: 200
}).start(v => {
element.style.transform = `scale(${v})`;
});
};
При работе с внешними источниками важно корректно управлять подписками:
const subscription = listen(document, "click").start(() => {
console.log("Click");
});
// Остановка
subscription.stop();
Это предотвращает:
Комплексный пример:
import { value, spring, listen } from "popmotion";
const scale = value(1, v => {
element.style.transform = `scale(${v})`;
});
// API
fetch("/scale")
.then(res => res.json())
.then(data => {
scale.update(data.value);
});
// Пользователь
listen(element, "mouseenter").start(() => {
spring({ to: 1.2 }).start(scale);
});
listen(element, "mouseleave").start(() => {
spring({ to: 1 }).start(scale);
});
В этом сценарии:
value для централизованного состоянияСинхронизация с внешними источниками превращает анимации из декоративного элемента в полноценную часть логики приложения, позволяя создавать отзывчивые и адаптивные интерфейсы.