skeleton —— Скелет
Скелет — это плейсхолдер «до завершения загрузки»: серые блоки имитируют грядущую раскладку, давая пользователю представление о том, «что он увидит через секунду». core/skeleton вычисляет последовательность ширин текстовых строк (последняя строка укорочена, как в реальном абзаце) и подготавливает сигнал доступности «это плейсхолдер, а не содержимое».
Когда использовать
Заголовок раздела «Когда использовать»- При реализации
LhSkeletonна каждой платформе: загрузка карточек, списков, статей. - Когда в любой области нужно выразить визуал «идёт загрузка»: скелет ближе к будущей раскладке, чем крутящийся loading.
- Не следует использовать: при очень короткой загрузке (меньше 300 мс) — мелькание скелета скорее мешает; а также для «нет данных» — это
LhEmpty.
import { getSkeletonClassNames, getSkeletonItemClassNames, getSkeletonRowWidths, getSkeletonAria,} from '@lh-ui/share/core/skeleton'import type { LhSkeletonState, LhSkeletonVariant } from '@lh-ui/share/core/skeleton'Замысел
Заголовок раздела «Замысел»getSkeletonRowWidths(rows) возвращает 60% для последней строки — деталь, но важная: последний строка реального текстового абзаца почти никогда не бывает полной. Скелет с одинаковой шириной всех строк выглядит как «полосатые обои», а не как «текст», и эти 60% придают плейсхолдеру «текстовость».
Последняя строка укорачивается только при rows > 1: единственная строка — это полноценная строка (укорачивание одиночной строки скорее похоже на обрезанный заголовок). При rows <= 0 возвращается пустой массив, без исключения.
getSkeletonAria() всегда возвращает { 'aria-hidden': true } — содержимое скелета несёт ноль информации, скринридер не должен его объявлять. Но одного скрытия недостаточно: комментарии в исходнике требуют, чтобы UI-слой одновременно навесил aria-busy="true" на внешний контейнер — тогда при готовности данных скринридер перечитает настоящее содержимое. Эти два атрибута составляют пару: aria-hidden убирает шум, aria-busy помечает «прочитать позже».
Таблица функций
Заголовок раздела «Таблица функций»| Функция | Сигнатура | Возврат | Назначение |
|---|---|---|---|
getSkeletonClassNames |
(state: LhSkeletonState) => string[] |
массив классов | анимация, модификатор с аватаром |
getSkeletonItemClassNames |
(variant: LhSkeletonVariant, animated: boolean) => string[] |
массив классов | форма блока-плейсхолдера и анимация |
getSkeletonRowWidths |
(rows: number) => string[] |
последовательность ширин | генерирует ширины текстовых строк |
getSkeletonAria |
() => LhAriaAttrs |
пакет aria-атрибутов | всегда { 'aria-hidden': true } |
Пример вывода:
getSkeletonRowWidths(3) // ['100%', '100%', '60%']getSkeletonRowWidths(1) // ['100%']getSkeletonRowWidths(0) // []getSkeletonItemClassNames('text', true)// ['lh-skeleton-item', 'lh-skeleton-item--text', 'lh-skeleton-item--animated']Определения типов
Заголовок раздела «Определения типов»export type LhSkeletonVariant = 'text' | 'rect' | 'circle'
export interface LhSkeletonState { animated: boolean avatar: boolean}Доступность
Заголовок раздела «Доступность»// Внешний контейнер в UI-слоеconst aria = { 'aria-busy': true } // пометка «содержимое грузится, после готовности перечитать»// Сам элемент скелетаconst skeletonAria = getSkeletonAria() // { 'aria-hidden': true }Типичная ошибка — сделать лишь одно из двух: только aria-hidden лишит пользователя скринридера нового содержимого после готовности данных (он решит, что уже прочитал); только aria-busy заставит скринридер попытаться объявить пустое содержимое скелета.
Примеры использования
Заголовок раздела «Примеры использования»import { getSkeletonRowWidths, getSkeletonItemClassNames } from '@lh-ui/share/core/skeleton'
getSkeletonRowWidths(4)// ['100%', '100%', '100%', '60%']
getSkeletonItemClassNames('circle', false)// ['lh-skeleton-item', 'lh-skeleton-item--circle']<!-- Vue3 --><div :class="getSkeletonClassNames({ animated, avatar })" aria-busy="true"> <div v-if="avatar" class="lh-skeleton__avatar"> <div :class="getSkeletonItemClassNames('circle', animated)" /> </div> <div class="lh-skeleton__content"> <div v-for="(w, i) in getSkeletonRowWidths(rows)" :key="i" :class="getSkeletonItemClassNames('text', animated)" :style="{ width: w }" v-bind="getSkeletonAria()" /> </div></div>// Reactconst widths = getSkeletonRowWidths(rows)return ( <div className={getSkeletonClassNames({ animated, avatar }).join(' ')} aria-busy={true}> <div className="lh-skeleton__content"> {widths.map((w, i) => ( <div key={i} className={getSkeletonItemClassNames('text', animated).join(' ')} style={{ width: w }} {...getSkeletonAria()} /> ))} </div> </div>)// uniapp: анимация на не-h5 платформах может быть плавной не везде;// UI-слой каждой платформы может отключать animated по ситуацииСтили и токены
Заголовок раздела «Стили и токены»| Класс | Семантика |
|---|---|
.lh-skeleton |
базовый класс: раскладка |
.lh-skeleton--animated |
включает shimmer-анимацию прокраски |
.lh-skeleton--with-avatar |
комбинированная раскладка «аватар + текст» (место слева под аватар) |
.lh-skeleton-item |
отдельный блок-плейсхолдер |
.lh-skeleton-item--{text|rect|circle} |
форма: текстовая строка / прямоугольник / круг |
.lh-skeleton-item--animated |
shimmer-анимация |
.lh-skeleton__avatar / .lh-skeleton__content |
область аватара и область содержимого |
Цвет блоков-плейсхолдеров — токены --lh-skeleton-* (серый чуть темнее фона + блик прокраски), в тёмной теме автоматически затемняется.
Замечания
Заголовок раздела «Замечания»- При
rows, равном 0 или отрицательному, возвращается пустой массив, и UI-слой не отрисовывает строки — это допустимый вход, а не ошибка. - Раскладка скелета должна совпадать с будущим реальным содержимым (та же структура карточки, то же число строк), иначе после загрузки произойдёт скачок раскладки. Использовать состояние
loadingуLhCardпроще, чем размещать скелет отдельно. - shimmer-анимация для некоторых пользователей является вестибулярным раздражителем; уважение к
prefers-reduced-motionлежит в слое стилей (отключение анимации через@media), core об этом не знает. aria-busyнужно вешать на контейнер данных, а не на сам скелет — после уничтожения скелетаaria-busyдолжен быть снят вместе с ним.
@lh-ui/share/core/skeleton → getSkeletonRowWidths( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue