Stagger эффекты

Stagger-эффекты представляют собой технику последовательного запуска анимаций для набора элементов с контролируемой задержкой между ними. В Popmotion эта концепция используется для создания каскадных переходов, где каждое последующее значение анимации стартует с небольшим сдвигом во времени относительно предыдущего.

В основе лежит идея управления не одной анимацией, а множеством связанных анимационных потоков, синхронизированных через вычисляемые задержки.


Модель работы stagger

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 предоставляет более гибкие композиционные инструменты.


Stagger как функция распределения

В Popmotion stagger может выступать как функция генерации задержек или значений для коллекций анимаций.

Обобщённая форма:

  • вход: массив элементов
  • выход: массив анимационных конфигураций с модифицированными параметрами

Пример логики:

const staggerDelay = (index, base = 50) => index * base;

Это позволяет централизовать управление временной шкалой.


Использование с animate

При работе с 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]

Такие модели позволяют создавать эффекты:

  • волны
  • раскрытие из центра
  • обратные каскады

Каскадные появления (fade-in cascade)

Один из наиболее распространённых сценариев — последовательное появление списка элементов.

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)`;
    }
  });
});

Такой подход часто используется в интерфейсах:

  • списки
  • таблицы
  • карточные сетки

Stagger с spring-анимацией

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 влияет только на старт, а дальнейшая динамика определяется физической моделью.


Двумерный stagger (grid-анимации)

При работе с сетками элементов применяется двухосевой подход:

  • индекс строки
  • индекс столбца

Формирование диагонального эффекта

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})`;
    }
  });
});

Результат — диагональная волна появления.


Радиальный stagger

Для создания эффекта распространения от центра используется расстояние до центральной точки.

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})`;
    }
  });
});

Такой подход используется для:

  • ripple-эффектов
  • появления UI-слоёв
  • визуализации данных

Комбинирование stagger и transform-цепочек

Stagger часто используется вместе с несколькими свойствами одновременно:

  • opacity
  • scale
  • translate
  • rotate
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)
      `;
    }
  });
});

Каскадная трансформация усиливает ощущение глубины и объёма.


Управление интенсивностью stagger

Ключевой параметр — шаг задержки.

Малый шаг

  • быстрые каскады
  • плотные интерфейсные эффекты

Большой шаг

  • явная последовательность
  • акцент на каждом элементе
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;
    }
  });
});

Stagger и производительность

При большом количестве элементов важны следующие аспекты:

  • каждая анимация создаёт отдельный тик обновления
  • частые пересчёты DOM могут стать узким местом
  • предпочтительно минимизировать количество style-инъекций

Оптимизационный подход:

  • кэширование ссылок на элементы
  • использование transform вместо layout-свойств
  • избегание forced reflow

Декларативные паттерны stagger

В архитектуре 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 в сложных интерфейсах

В реальных интерфейсах stagger применяется не изолированно, а как часть композиции:

  • входные анимации страниц
  • появление модальных окон
  • раскрытие меню
  • загрузочные состояния списков
  • анимация графиков

Каждый элемент UI может иметь собственную задержку, но общий контроль осуществляется через единую стратегию распределения времени.


Синхронизация stagger с таймлайнами

При построении сложных сцен stagger интегрируется в общую временную шкалу:

  • глобальное время сцены
  • локальные offset-группы
  • независимые сегменты анимаций

Это позволяет создавать многослойные визуальные сценарии, где разные группы элементов входят в сцену с разными ритмами.