Skip to content
PПромтбук
RUEN
02Motion

Stagger reveal списков: оркестровка

Тайминг 60-100ms между элементами, easing, max-batch 12, performance-бюджет и accessibility.

Спроектируй stagger reveal для списка из {{item_count}} элементов на стеке {{framework}}. Цель — оркестровка, которая «дышит», а не выглядит как заводская конвейерная лента.

1. Timing scale

Длительность одного элемента vs. задержка между ними:

item countper-item durationstagger delaytotal duration
3-4300ms100ms~600ms
5-8250ms80ms~750-800ms
9-12200ms60ms~900-1000ms
>12НЕ staggerone fade 200ms

Правило: total duration ≤ 1.0s. Если выходит больше — уменьшай stagger delay, не per-item.

2. Easing

/* enter: ease-out — быстро появляется, мягко тормозит */
--ease-out-stagger: cubic-bezier(0.22, 1, 0.36, 1);

/* exit (если нужно): ease-in, обратный порядок, быстрее */
--ease-in-stagger: cubic-bezier(0.5, 0, 0.75, 0);

Стандартная связка: enter = ease-out + opacity + translateY 12-16px; exit = ease-in + opacity, без движения.

3. Framer Motion паттерн

const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.08, delayChildren: 0.05 }, // 80ms между
  },
};

const item = {
  hidden: { opacity: 0, y: 12 },
  visible: {
    opacity: 1, y: 0,
    transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] },
  },
};

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.slice(0, 12).map((it) => (
    <motion.li key={it.id} variants={item}>{it.label}</motion.li>
  ))}
</motion.ul>

4. Max-batch правило

  • Не stagger более 12 элементов. После 12 — пользователь воспринимает уже не «оркестровку», а раздражающее ожидание.
  • Для длинных списков (20+): первые 6-8 элементов stagger, остальные — мгновенно (transition: { delay: 0.5 } или просто fade-in container'а).
  • Виртуализованные списки: stagger только видимые в viewport, скролл — без анимации.

5. Performance бюджет

  • Только opacity + transform (composite layer). Никаких height, top, box-shadow.
  • Цель: 60 FPS на mid-range Android (Moto G Power class). Замерь через DevTools Performance → FPS meter.
  • При 12 элементах одновременно в анимации — это ~12 composite layers. Браузеру ок, GPU памяти — тоже.
  • will-change: transform, opacityтолько на время анимации, потом снимай (иначе утечка GPU-памяти).

6. Accessibility

const shouldReduce = useReducedMotion();
const stagger = shouldReduce ? 0 : 0.08;
const itemDuration = shouldReduce ? 0.15 : 0.25;
const itemY = shouldReduce ? 0 : 12;

При reduced-motion: stagger=0, все элементы fade-in одновременно за 150ms. Без translateY.

7. Когда stagger вреден

  • Списки, которые часто обновляются (чат, лог, фильтр-результаты при типинге) — мигание раздражает.
  • Первая загрузка above-the-fold контента — stagger задерживает LCP. Используй mgnovenный рендер + анимация только для below-fold.
  • Таблицы и data-grid — пользователь сканирует, ему не нужна оркестровка.

8. Формат вывода

## Spec
- Item count: 8
- Per-item: 250ms, ease-out, opacity + y(12→0)
- Stagger: 80ms
- Total: ~810ms
- Reduced-motion: 150ms simultaneous fade

## Реализация
[код]

## Verification
- [ ] 60 FPS на throttled CPU 4x
- [ ] Reduced-motion ветка проверена
- [ ] Total ≤ 1s

Анти-паттерны

  • ❌ Stagger на 20+ элементов — выглядит как загрузка из 2005-го.
  • ❌ Per-item duration 600ms + stagger 200ms × 10 элементов = 2.6 секунды задержки до полного списка.
  • ❌ Анимация height или top вместо transform — layout thrashing, 30 FPS на mobile.
  • ❌ Stagger на above-the-fold hero list — убивает LCP на 500-800ms.
  • ❌ Одинаковый easing для enter и exit — выглядит ватно, нет жизни.
  • will-change: transform навсегда — съедает GPU-память.
  • ❌ Stagger в виртуализованном списке при скролле — каждый scroll triggering анимацию = jank.
  • ❌ Игнор prefers-reduced-motion — оркестровка по оси Y особенно триггерит вестибулярных пользователей.
К подразделу «Motion»
Похожие промты