Спроектируй stagger reveal для списка из {{item_count}} элементов на стеке {{framework}}. Цель — оркестровка, которая «дышит», а не выглядит как заводская конвейерная лента.
1. Timing scale
Длительность одного элемента vs. задержка между ними:
| item count | per-item duration | stagger delay | total duration |
|---|---|---|---|
| 3-4 | 300ms | 100ms | ~600ms |
| 5-8 | 250ms | 80ms | ~750-800ms |
| 9-12 | 200ms | 60ms | ~900-1000ms |
| >12 | НЕ stagger | — | one 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 особенно триггерит вестибулярных пользователей.
Аудит производительности (Core Web Vitals)
Глубокая проверка LCP, INP, CLS с привязкой к коду и приоритизированным планом исправлений.
Мастер-аудит сайта: 6 измерений за один проход
Orchestrator-аудит по 6 направлениям: UX, accessibility, performance, SEO, brand consistency, security. Quick scan + deep dive + приоритизированный план + композитная оценка + roadmap.
Performance budget по типам страниц
Бюджеты JS/CSS/images для разных типов страниц, целевые Web Vitals, enforcement в CI с конкретными порогами.