Stagger-эффекты представляют собой технику последовательного запуска анимаций для набора элементов с контролируемой задержкой между ними. В Popmotion эта концепция используется для создания каскадных переходов, где каждое последующее значение анимации стартует с небольшим сдвигом во времени относительно предыдущего.
В основе лежит идея управления не одной анимацией, а множеством связанных анимационных потоков, синхронизированных через вычисляемые задержки.
Stagger в Popmotion опирается на детерминированное распределение задержек по индексам элементов коллекции.
Общая форма вычисления задержки:
iЭто формирует линейную или модифицируемую последовательность запусков.
Классический stagger строится на простой зависимости:
import { animate } from "popmotion";
const elements = document.querySelectorAll(".item");
elements.forEach((el, i) => {
animate({
from: 0,
to: 1,
duration: 400,
delay: i * 80,
onUpdate: v => {
el.style.opacity = v;
el.style.transform = `translateY(${(1 - v) * 20}px)`;
}
});
});
Здесь stagger реализован вручную через delay, однако
Popmotion предоставляет более гибкие композиционные инструменты.
В Popmotion stagger может выступать как функция генерации задержек или значений для коллекций анимаций.
Обобщённая форма:
Пример логики:
const staggerDelay = (index, base = 50) => index * base;
Это позволяет централизовать управление временной шкалой.
При работе с animate stagger часто применяется через
динамический delay.
import { animate } from "popmotion";
const boxes = document.querySelectorAll(".box");
boxes.forEach((box, index) => {
animate({
from: { scale: 0, opacity: 0 },
to: { scale: 1, opacity: 1 },
duration: 600,
delay: index * 60,
onUpdate: latest => {
box.style.transform = `scale(${latest.scale})`;
box.style.opacity = latest.opacity;
}
});
});
Ключевая особенность — каждый анимационный поток независим, но синхронизирован по времени старта.
Stagger может быть:
[ delay(i) = i step]
[ delay(i) = (n - i) step]
[ delay(i) = |i - mid| step]
Такие модели позволяют создавать эффекты:
Один из наиболее распространённых сценариев — последовательное появление списка элементов.
const items = document.querySelectorAll(".list-item");
items.forEach((item, i) => {
animate({
from: { opacity: 0, y: 10 },
to: { opacity: 1, y: 0 },
duration: 500,
delay: i * 40,
onUpdate: v => {
item.style.opacity = v.opacity;
item.style.transform = `translateY(${v.y}px)`;
}
});
});
Такой подход часто используется в интерфейсах:
Popmotion поддерживает физически основанные анимации, и stagger может применяться поверх spring-модели.
import { animate, spring } from "popmotion";
const cards = document.querySelectorAll(".card");
cards.forEach((card, i) => {
animate({
from: 0,
to: 1,
delay: i * 50,
ease: spring({ stiffness: 120, damping: 14 }),
onUpdate: v => {
card.style.transform = `scale(${v})`;
}
});
});
Здесь stagger влияет только на старт, а дальнейшая динамика определяется физической моделью.
При работе с сетками элементов применяется двухосевой подход:
const grid = document.querySelectorAll(".cell");
const cols = 10;
grid.forEach((cell, index) => {
const row = Math.floor(index / cols);
const col = index % cols;
const delay = (row + col) * 40;
animate({
from: 0,
to: 1,
delay,
onUpdate: v => {
cell.style.opacity = v;
cell.style.transform = `scale(${v})`;
}
});
});
Результат — диагональная волна появления.
Для создания эффекта распространения от центра используется расстояние до центральной точки.
const items = document.querySelectorAll(".dot");
const size = Math.sqrt(items.length);
const center = size / 2;
items.forEach((item, index) => {
const x = index % size;
const y = Math.floor(index / size);
const distance = Math.sqrt(
Math.pow(x - center, 2) + Math.pow(y - center, 2)
);
animate({
from: 0,
to: 1,
delay: distance * 80,
onUpdate: v => {
item.style.opacity = v;
item.style.transform = `scale(${v})`;
}
});
});
Такой подход используется для:
Stagger часто используется вместе с несколькими свойствами одновременно:
elements.forEach((el, i) => {
animate({
from: { opacity: 0, rotate: -10, y: 20 },
to: { opacity: 1, rotate: 0, y: 0 },
duration: 700,
delay: i * 30,
onUpdate: v => {
el.style.opacity = v.opacity;
el.style.transform = `
translateY(${v.y}px)
rotate(${v.rotate}deg)
`;
}
});
});
Каскадная трансформация усиливает ощущение глубины и объёма.
Ключевой параметр — шаг задержки.
const baseStep = 120;
items.forEach((item, i) => {
const delay = Math.min(i * baseStep, 1000);
animate({
from: 0,
to: 1,
delay,
onUpdate: v => {
item.style.opacity = v;
}
});
});
При большом количестве элементов важны следующие аспекты:
Оптимизационный подход:
transform вместо layout-свойствВ архитектуре UI stagger часто абстрагируется в функцию:
const stagger = (items, fn, step = 50) => {
items.forEach((item, i) => {
fn(item, i, i * step);
});
};
Использование:
stagger(buttons, (btn, i, delay) => {
animate({
from: 0,
to: 1,
delay,
onUpdate: v => {
btn.style.opacity = v;
}
});
});
Это упрощает повторное использование логики.
В реальных интерфейсах stagger применяется не изолированно, а как часть композиции:
Каждый элемент UI может иметь собственную задержку, но общий контроль осуществляется через единую стратегию распределения времени.
При построении сложных сцен stagger интегрируется в общую временную шкалу:
Это позволяет создавать многослойные визуальные сценарии, где разные группы элементов входят в сцену с разными ритмами.