GrSkeleton

Пакет: @feugene/granularityядроГруппа: Обратная связь

Берут, когда контента ещё нет.

Когда брать

  • контента ещё нет — заглушка повторяет его форму, и раскладка не прыгает при появлении данных;
  • загрузка первой отрисовки — карточка, список, таблица: пользователь видит структуру до данных;
  • заглушек несколькоcount повторяет строку нужное число раз;
  • форма нестандартнаяvariant даёт полосу, блок или круг под аватар.

Когда взять другое

НужноБерите
Контент уже есть, но обновляетсяGrLoading
Известна доля выполненногоGrProgressBar
Загрузка закончилась, данных нетGrEmptyState
Ожидание внутри кнопкиGrButton с loading

Скелетон честен только тогда, когда повторяет будущую раскладку. Три одинаковые полосы вместо таблицы из шести колонок обманывают дважды: сначала обещают не то, потом дают прыжок раскладки.

Форма — вариантом, размеры — потребителем

variantРадиусДля чего
text (по умолчанию)--gr-radius-fullстрока текста
rect--gr-radius-mdблок: карточка, кнопка, картинка
circle--gr-radius-fullаватар, иконка

Вариант задаёт только форму. width и height остаются на потребителе: высоту заглушки диктует контент, который она подменяет, а не шкала компонента. Дефолт — 100% × 12px, у круга — 2.5rem.

Круг — единственная форма, где размеры связаны: height повторяет width, пока её не задали явно. Иначе одна заданная сторона превращала бы круг в овал.

rounded перекрывает радиус варианта точечно — например, когда у карточки приложения свой крупный радиус.

Блок из нескольких строк

<GrSkeleton :count="3" />

count рисует N заглушек в общей сетке. У text последняя строка короче (60%): блок читается как абзац, а не как список одинаковых полос. У rect и circle все элементы одинаковые — «недописанной строки» у них не бывает. Заданный width сильнее этого правила.

Одна заглушка (count по умолчанию) рендерится без обёртки — ровно тем же узлом, что и раньше.

Доступность

Заглушка целиком помечена aria-hidden="true" — и одиночная, и обёртка группы. Озвучивать «пустой» узел незачем; о том, что идёт загрузка, сообщает контейнер: aria-busy="true" и живой регион с текстом. Так это сделано внутри пакета — см. состояние loading у GrStatistic и GrTable.

Движение

Пульсация — собственный @keyframes компонента, и под prefers-reduced-motion: reduce она выключается полностью (animation: none), а не ускоряется: бесконечная анимация — это движение, которого пользователь попросил не показывать. Контракт движения целиком — ../motion.md.

<style> у компонента без scoped намеренно: правило висит на атрибуте [data-gr-skeleton], поэтому доезжает и до заглушек, отрисованных внутри других компонентов пакета.

Playground 5

Загружается…

Код
<GrSkeleton />

Установка

npm i @feugene/granularity

Импорт

import { GrSkeleton } from '@feugene/granularity/components/GrSkeleton'

API

Props

PropTypeпо умолчаниюОписание
variant"circle" | "text" | "rect" | undefined"text"Форма, а не размеры: `width`/`height` остаются на потребителе, потому что высота заглушки диктуется соседним контентом, а не шкалой компонента.
widthstring | undefinedundefined
heightstring | undefinedundefined
roundedstring | undefinedundefinedРадиус точечно. Не задан — берётся из `variant`.
countnumber | undefined1Сколько заглушек подряд. Больше одной — они уезжают в общую обёртку.

Примеры 4

Форма и повторение

variant задаёт форму, а не размеры: text — пилюля, rect — скруглённый блок, circle — круг (высота повторяет ширину). Размеры остаются на потребителе, потому что высоту заглушки диктует соседний контент. count рисует блок из N заглушек одним пропом; у text последняя строка короче, поэтому блок читается абзацем, а не списком одинаковых полос.

variant="text"
variant="rect"
variant="circle"
:count="4"

Variants
<script setup lang="ts">
import { GrSkeleton } from '@feugene/granularity'
</script>

<template>
  <div class="grid gap-4">
    <div class="grid gap-3 sm:grid-cols-3">
      <div class="grid gap-2">
        <span class="text-xs text-[var(--gr-muted-fg)]">variant="text"</span>
        <GrSkeleton />
      </div>

      <div class="grid gap-2">
        <span class="text-xs text-[var(--gr-muted-fg)]">variant="rect"</span>
        <GrSkeleton variant="rect" height="40px" />
      </div>

      <div class="grid gap-2">
        <span class="text-xs text-[var(--gr-muted-fg)]">variant="circle"</span>
        <GrSkeleton variant="circle" width="40px" />
      </div>
    </div>

    <div class="grid gap-2">
      <span class="text-xs text-[var(--gr-muted-fg)]">:count="4"</span>
      <GrSkeleton :count="4" />
    </div>
  </div>
</template>

Одна заглушка рендерится без обёртки — DOM тех, кто уже использует GrSkeleton, от появления count не меняется. Заглушка целиком aria-hidden: загрузку объявляет контейнер (aria-busy + живой регион), а не полоса.

Заглушка текстовой карточки

Базовый loading-surface для статей, карточек и описательных блоков: заголовок variant="rect" и абзац одним :count="3" вместо трёх строк вручную.

Text Card
<script setup lang="ts">
import { GrCard, GrSkeleton } from '@feugene/granularity'
</script>

<template>
  <GrCard class="grid gap-4 p-4">
    <GrSkeleton variant="rect" width="38%" height="20px" />

    <!-- Три строки одним пропом: последняя короче, поэтому блок читается абзацем. -->
    <GrSkeleton :count="3" />
  </GrCard>
</template>

Заглушки строки списка с аватаром

Data-display сценарий для таблиц и списков: avatar, две текстовые строки и trailing action area.

List
<script setup lang="ts">
import { GrSkeleton } from '@feugene/granularity'

const rows = [1, 2, 3]
</script>

<template>
  <div class="grid gap-3 rounded-xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
    <div v-for="row in rows" :key="row" class="flex items-center gap-3">
      <GrSkeleton variant="circle" width="44px" />

      <div class="min-w-0 flex-1 grid gap-2">
        <GrSkeleton width="44%" />
        <GrSkeleton width="72%" />
      </div>

      <GrSkeleton variant="rect" width="72px" height="28px" />
    </div>
  </div>
</template>

Раскладка дашборда и графика

Комбинируем разные размеры GrSkeleton, чтобы быстро собрать loading-layout для dashboard, chart и KPI blocks.

Dashboard
<script setup lang="ts">
import { GrSkeleton } from '@feugene/granularity'
</script>

<template>
  <div class="grid gap-3 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
    <div class="grid gap-3 rounded-xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
      <GrSkeleton width="26%" height="18px" rounded="12px" />
      <GrSkeleton height="160px" rounded="18px" />
      <div class="grid gap-2 sm:grid-cols-3">
        <GrSkeleton v-for="card in 3" :key="card" height="72px" rounded="16px" />
      </div>
    </div>

    <div class="grid gap-3 rounded-xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
      <GrSkeleton width="48%" height="18px" rounded="12px" />
      <GrSkeleton :count="4" width="100%" />
    </div>
  </div>
</template>

Документация компонентаВсе компоненты