Stagger-эффекты

Понятие stagger-анимации

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

В контексте DOM-анимаций stagger применяется к массивам элементов: спискам, сеткам, карточкам интерфейса, таблицам, меню. Основная идея заключается в вычислении задержки старта анимации на основе позиции элемента в наборе.

Motion One предоставляет встроенную поддержку stagger через функцию stagger(), интегрированную в систему animate() и timeline().


Базовая механика stagger в Motion One

Функция stagger() возвращает функцию расчёта задержки для каждого элемента коллекции.

import { animate, stagger } from "motion";

animate(
  ".item",
  { opacity: [0, 1], transform: ["translateY(20px)", "none"] },
  { delay: stagger(0.1) }
);

В этом примере:

  • каждый элемент .item получает индивидуальную задержку
  • базовый шаг составляет 0.1 секунды
  • порядок определяется порядком элементов в DOM

Механизм работает через индекс элемента в коллекции: delay = index * interval


Контроль направления и порядка

Stagger поддерживает изменение направления распределения задержек.

animate(
  ".item",
  { opacity: [0, 1] },
  { delay: stagger(0.08, { from: "last" }) }
);

Параметр from определяет точку начала:

  • "first" — стандартный порядок
  • "last" — обратный порядок
  • число — индекс стартового элемента
  • "center" — распространение от центра

Центрированный stagger

При работе с сетками или симметричными интерфейсами используется распространение от центра:

animate(
  ".card",
  { scale: [0.8, 1], opacity: [0, 1] },
  { delay: stagger(0.05, { from: "center" }) }
);

Поведение зависит от индексации элементов: центральные запускаются первыми, крайние — последними, формируя радиальный эффект.


Stagger в timeline-анимациях

Timeline позволяет комбинировать stagger с последовательными и параллельными анимациями.

import { timeline, stagger } from "motion";

timeline([
  [".header", { opacity: [0, 1] }, { duration: 0.4 }],
  [
    ".item",
    { y: [30, 0], opacity: [0, 1] },
    { delay: stagger(0.06) }
  ],
  [".footer", { opacity: [0, 1] }, { duration: 0.3 }]
]);

Stagger внутри timeline действует локально для текущего шага и не влияет на другие сегменты.


Динамический stagger через функцию

Задержка может вычисляться через пользовательскую функцию, что позволяет реализовать сложные зависимости:

animate(
  ".tile",
  { opacity: [0, 1] },
  {
    delay: (i, total) => {
      return (total - i) * 0.03;
    }
  }
);

Параметры функции:

  • i — индекс элемента
  • total — общее количество элементов

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

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

Stagger по координатам (сетки и матрицы)

Для сеточных структур применяется расчёт на основе координат, а не линейного индекса.

const items = document.querySelectorAll(".grid-item");

animate(
  items,
  { opacity: [0, 1], scale: [0.9, 1] },
  {
    delay: stagger(0.04, {
      from: [2, 2]
    })
  }
);

Значение from в виде массива интерпретируется как координаты точки распространения. Это создаёт эффект «вспышки» или «волны» от заданной позиции.


Комбинация stagger с easing и keyframes

Stagger управляет только временем старта, в то время как easing и keyframes формируют характер движения.

animate(
  ".dot",
  {
    y: [50, 0],
    opacity: [0, 1]
  },
  {
    delay: stagger(0.03),
    easing: "ease-out",
    duration: 0.6
  }
);

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


Перекрывающиеся stagger-эффекты

При сложной композиции возможно наложение нескольких stagger-паттернов через разные селекторы.

animate(".row-1 .cell", {
  opacity: [0, 1]
}, { delay: stagger(0.02) });

animate(".row-2 .cell", {
  opacity: [0, 1]
}, { delay: stagger(0.02, { from: "last" }) });

Так создаётся контраст направлений между группами элементов.


Stagger и входные/выходные состояния

Stagger одинаково применяется для появления и исчезновения элементов.

animate(
  ".menu-item",
  { opacity: [1, 0], x: [0, -10] },
  { delay: stagger(0.05, { from: "last" }) }
);

Обратный порядок усиливает эффект «сворачивания» интерфейса.


Управление интенсивностью задержек

Интервал stagger может быть адаптирован под количество элементов. При большом числе узлов применяется уменьшение шага:

const items = document.querySelectorAll(".list-item");

animate(
  items,
  { opacity: [0, 1] },
  {
    delay: stagger(0.2 / items.length)
  }
);

Так сохраняется одинаковая общая длительность анимации независимо от размера списка.


Stagger в сложных UI-композициях

В интерфейсах с несколькими слоями применяется каскадное распределение:

  • заголовок
  • подзаголовок
  • блоки контента
  • элементы списка
import { animate, stagger } from "motion";

animate(".title", { opacity: [0, 1] }, { duration: 0.4 });

animate(".subtitle", { opacity: [0, 1] }, { delay: 0.2 });

animate(".card", {
  opacity: [0, 1],
  y: [20, 0]
}, {
  delay: stagger(0.05)
});

Комбинация последовательных и stagger-анимаций формирует многоуровневую динамику интерфейса.


Производительность stagger-анимаций

Stagger в Motion One работает без промежуточных таймеров на каждый элемент. Все задержки вычисляются заранее и передаются в единый план анимации.

Это снижает нагрузку на:

  • event loop
  • requestAnimationFrame очереди
  • управление таймерами

Особенно заметно при больших списках (100+ элементов), где альтернативные подходы с setTimeout приводят к деградации производительности.


Ограничения и особенности поведения

  • порядок зависит от NodeList или массива элементов
  • динамическое добавление элементов требует повторного вызова анимации
  • stagger не синхронизируется автоматически с изменением DOM
  • значения from интерпретируются относительно текущего набора элементов

Сочетание stagger с пользовательскими трансформациями

Stagger часто используется вместе с вычисляемыми трансформациями:

animate(
  ".box",
  (i) => ({
    transform: [
      `translateY(${i * 2}px) scale(${1 - i * 0.02})`,
      "none"
    ]
  }),
  {
    delay: stagger(0.04)
  }
);

Индекс элемента становится параметром геометрии анимации, усиливая глубину композиции.