Skip to content
PПромтбук
RUEN
02UI-компоненты

Оркестрация loading-состояний

Skeleton vs spinner vs progressive: бюджет тайминга, выбор техники, переходы без скачков.

Спроектируй оркестрацию loading для: {{screen}}. Источники: {{data}}.

Тезис: loading — не "что-то крутится". Это бюджет тайминга + выбор техники под форму данных + переходы, чтобы не было скачков.

1. Бюджет тайминга (правило 100/1000/10000)

ДлительностьВосприятиеТехника
< 100msмгновенноничего не показывай — оверлей мерцает
100-300msбыстро200ms fade-in; не показывай тяжёлый skeleton
300ms-1sзаметноskeleton ИЛИ inline-spinner
1-3s"грузится"skeleton обязательно; для длинных списков — virtualized
3-10s"долго"skeleton + контекст ("Получаем 1240 записей…")
> 10s"сломалось"прогресс-бар обязателен; кнопка отмены; иначе пользователь уходит

Не уверен в длительности — измерь p50/p95 на проде. Дизайнить под "обычно быстро" — обманывать пользователя на p95.

2. Decision tree: какую технику

Известен прогресс (загрузка файла, multi-step)?
  └── Да → progress bar (детерминированный)
  └── Нет ↓

Форма результата предсказуема (список карточек, таблица)?
  └── Да → skeleton, повторяющий форму
  └── Нет ↓

Это сабмит / действие (кнопка, форма)?
  └── Да → inline-spinner В кнопке + disable
  └── Нет ↓

Это первичная навигация (route change)?
  └── Да → top progress bar (NProgress-стиль) + skeleton ниже
  └── Нет ↓

Это refresh уже видимых данных?
  └── Да → subtle indicator (точка/spinner в углу), НЕ перерисовывай контент
  └── Нет → spinner с подписью контекста

3. Skeleton — правила формы

  • Силуэт совпадает по форме с контентом: высота строк, число колонок, размеры аватаров
  • Ширина текстовых "плашек" вариативна (60%, 80%, 45%) — на пустых местах глаз ловит фальшь
  • Радиусы и spacing — те же токены, что и в реальном UI
  • Анимация — background-position shimmer ИЛИ opacity pulse, не оба
  • Длительность анимации 1.4-1.8s; быстрее — нервно
  • prefers-reduced-motion → выключи shimmer, оставь statiс серый
.skeleton {
  background: linear-gradient(90deg,
    var(--bg-subtle) 0%,
    var(--bg-elevated) 50%,
    var(--bg-subtle) 100%);
  background-size: 200% 100%;
  animation: shimmer 1.6s linear infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
}

4. Spinner — правила

  • Размер 16/20/24px (контекст: в кнопке, в инпуте, на overlay)
  • Толщина обводки = 2px при 16-20, 2.5-3px при 24+
  • Анимация rotate(360deg) linear 0.7-1s infinite — медленнее провоцирует "висит", быстрее раздражает
  • В кнопке — слева от лейбла, лейбл меняется на "Сохраняем…", кнопка disabled

5. Progress bar — правила

  • Только если знаешь прогресс. Не делай fake-progress.
  • Если шагов несколько (upload-OCR-parse) — покажи стадию ("Распознаём…")
  • 0% → 100% линейно — не fake "почти готово"
  • Если шаг непредсказуем по длине — переключи на indeterminate bar (animation), не оставляй 90% мёртво

6. Переходы без скачков

Главная боль loading — CLS (layout shift) когда контент пришёл.

  • Skeleton строго совпадает по высоте и ширине с конечным контентом
  • Используй min-height на контейнере, чтобы первый кадр данных не подпрыгивал
  • Fade-in данных 150ms, fade-out skeleton 150ms — кроссфейд
  • Картинки — aspect-ratio зарезервирован до загрузки
{isLoading ? (
  <SkeletonGrid count={6} />
) : (
  <Grid items={data} className="animate-fadein" />
)}

7. Оркестрация нескольких запросов

Один экран — несколько запросов разной скорости. Выбор:

  • All-or-nothing. Жди все, потом покажи. Только если части бессмысленны по отдельности.
  • Streamed. Каждая секция отдельный skeleton; готовые появляются. Дефолт для дашбордов.
  • Above-the-fold first. Главный блок — приоритет, остальное чуть позже. Использует Suspense / lazy / приоритеты запросов.

Анти-паттерн: верх ушёл, низ грузится — на каждый низ свой spinner ОДНОВРЕМЕННО. Лучше один общий статус "Подгружаем 3 раздела".

8. Optimistic UI (когда применимо)

  • Применимо: like, toggle, sort, drag-reorder, добавление в корзину
  • Не применимо: платежи, удаление с подтверждением, что угодно с настоящими последствиями
  • На ошибке — откат + toast с retry. Контент не пропадает между.

9. Скрытый чек-лист (часто забывают)

  • Первый paint не пустой — есть skeleton ИЛИ закешированные данные ИЛИ статичный shell
  • Если данные пришли за < 100ms — skeleton НЕ мелькнул (debounce появления на 100ms)
  • Skeleton имеет aria-busy="true" и role="status"
  • aria-live="polite" объявляет завершение для screen reader
  • Кнопка отмены для всего, что > 3s
  • На retry — старые данные на экране, новые подгружаются в фоне (не "пусто на 2с")
  • Reduced-motion уважается
  • Tested на throttled 3G (DevTools)

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

  1. Тайминг-бюджет для каждого блока экрана (мс p50/p95 → техника)
  2. Decision tree применённый к {{screen}}: что какой техникой
  3. JSX-скелет skeleton-компонента, совпадающего с реальным
  4. Стратегия оркестрации для {{data}} (all/streamed/above-the-fold)
  5. Чек-лист с галочками что покрыто

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

  • Spinner поверх skeleton (двойная нагрузка глаз)
  • Fake-progress, который застывает на 95%
  • Skeleton мелькает 60ms — раздражает, ничего не сообщает
  • На рефреш — пустой экран и заново skeleton; пользователь думает, всё пропало
  • "Loading..." без контекста на 5 секунд
  • Skeleton не совпадает по размеру → layout прыгает на финале
  • Анимация skeleton 4s — выглядит "сломано", не "грузится"
К подразделу «UI-компоненты»
Похожие промты