コンテンツにスキップ

skeleton —— スケルトン

スケルトンは「読み込み完了前」のプレースホルダーです:灰色のブロックで間もなく現れるレイアウトをシミュレートし、ユーザーに「この後に何が見えるか」の予測を持たせます。core/skeleton はテキスト行の幅のシーケンス(末行は短く、実際の段落のように)を計算し、「これはプレースホルダーであって内容ではない」というアクセシビリティのシグナルを準備します。

  • 各端が LhSkeleton を実装する:カードの読み込み、リストの読み込み、文章の読み込み。
  • 任意の領域で「読み込み中」の視覚を表現したい場合、スケルトンは loading の回転よりも「これからのレイアウトに近い」ものです。
  • 使うべきでない:読み込み時間が極めて短い(300ms 未満)ケース——一瞬表示されるスケルトンはかえって邪魔です。「データが空」にも使わないでください、それは 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 のときだけ末行を短くします:1 行しかない場合はそれで完全な 1 行です(1 行だけ短くすると逆にタイトルが truncate されたように見えます)。rows <= 0 は空の配列を返し、エラーを投げません。

getSkeletonAria() は常に { 'aria-hidden': true } を返します——スケルトンの内容は情報量ゼロで、スクリーンリーダーが読み上げるべきではありません。しかし隠すだけでは不十分です:ソースコードのコメントは UI 層に対し、外側のコンテナに aria-busy="true" を同時に取り付けることを求めています。これによりデータの準備が整ったときにスクリーンリーダーが実際の内容を改めて読み上げます。この 2 つの属性は一対です: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>
// React
const 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 スキャンアニメーション
.lh-skeleton__avatar / .lh-skeleton__content アバターエリアとコンテンツエリア

プレースホルダーブロックの色は --lh-skeleton-* トークンを使い(背景よりやや濃いグレー + スキャンのハイライト)、ダークモードでは自動的に暗くします。

  • rows が 0 または負数のときは空の配列を返し、UI 層は行をレンダリングしません——これは正当な入力であり、エラーではありません。
  • スケルトンのレイアウトは実際の内容と一致すべき(同じカード構造、同じ行数)です。そうでないと読み込み完了時にレイアウトが跳ねます。LhCard の loading 状態を使うとスケルトンを単独で置くより手間がかかりません。
  • shimmer アニメーションは一部のユーザーにとって前庭刺激の原因になります;prefers-reduced-motion を尊重する責任はスタイル層(@media の中でアニメーションを無効化)にあり、core 層は関知しません。
  • aria-busy はデータコンテナに取り付けるべきで、スケルトン自身ではありません——スケルトンが破棄されたら aria-busy も一緒に取り除きます。
@lh-ui/share/core/skeleton → getSkeletonRowWidths( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue