CSSで読み込み中のスケルトン表示を作るには
本物の内容と同じ大きさの灰色の箱を並べ、linear-gradient の明るい帯を background-position のアニメーションで流して、読み込み中のスケルトン表示を作る。
中身が届く前から配置が決まるので、差し替えたときに周りのレイアウトが動かない。
実行確認済みは、ブラウザで実際に描画して見た目を確認したものである。
公開:
<div class="card">
<span class="sr-only" role="status">記事を読み込み中…</span>
<div class="skeleton skeleton--avatar" aria-hidden="true"></div>
<div class="card__body" aria-hidden="true">
<div class="skeleton skeleton--title"></div>
<div class="skeleton skeleton--text"></div>
<div class="skeleton skeleton--text skeleton--short"></div>
</div>
</div>.card {
display: flex;
gap: 12px;
max-width: 360px;
padding: 16px;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
.card__body {
flex: 1;
}
/* 灰色の地に明るい帯を 1 本入れたグラデーションを、要素の 2 倍の幅に広げる */
.skeleton {
border-radius: 6px;
background: linear-gradient(90deg, #e2e8f0 25%, #f8fafc 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.4s ease-in-out infinite;
}
.skeleton--avatar {
flex: none;
width: 48px;
height: 48px;
border-radius: 50%;
}
.skeleton--title {
width: 60%;
height: 16px;
margin-bottom: 12px;
}
.skeleton--text {
height: 12px;
margin-bottom: 8px;
}
.skeleton--short {
width: 40%;
}
/* 背景の位置をずらして、明るい帯を左から右へ流す */
@keyframes shimmer {
from {
background-position: 100% 0;
}
to {
background-position: -100% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: none;
background: #e2e8f0;
}
}
/* 見た目には出さず、読み上げだけに残す */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}グラデーションを background-size: 200% で要素の 2 倍の幅に広げ、background-position を動かすと、明るい帯が箱の上を流れる。
箱の幅と高さを本物の見出しや本文の行に合わせておくのは、読み込みが終わって中身に差し替えたときに、下にある要素が押し下げられないようにするためである。
つまずき
帯が動かないときは、まず background-size を確かめるとよい。
グラデーションの背景は既定で要素と同じ大きさに描かれ、背景と要素の大きさが同じだと background-position のパーセント指定は位置を変えない。
上の例のように background-size: 200% 100% で横に 2 倍に広げると、はみ出した分だけ動かせるようになる。
箱が見えないときは、高さを与えているかを見る。
中身の無い div は高さが 0 になり、span のようなインライン要素は width と height を無視するので、display: block や flex アイテムにしてから大きさを指定する。
本物の内容と同じ大きさにする
スケルトンは「何かを読み込んでいる」と伝えるだけでなく、中身が入る場所を先に確保する役目も持つ。
箱の高さを本物の見出しや本文の行の高さに、幅を想定される文字数に近づけておくと、データが届いて差し替えたときに、その下にある要素の位置が変わらない。
大きさが合っていないと、差し替えた瞬間に下の段落やボタンが押し下げられ、押そうとした場所がずれる。
行数が決まらない本文は、最後の行だけ短くしておくと文章らしく見える。
動きを控えたい利用者への配慮
流れ続ける帯は、動きに敏感な利用者にとって不快の原因になりうる。
上の例では prefers-reduced-motion: reduce のときに animation: none とし、帯の無い灰色一色の箱だけを残している。
スピナーと違い、スケルトンは動かなくても箱の形で「ここに内容が入る」ことが伝わるので、動きを止めても情報はほとんど失われない。
スクリーンリーダーへの伝え方
灰色の箱そのものには意味が無いので、上の例では aria-hidden="true" で読み上げの対象から外し、代わりに見た目には出さない「記事を読み込み中…」という文言を role="status" の要素に置いている。
role="status" のような live region は、中身が変わったことを読み上げる仕組みである。
live region の要素ごと後から差し込むと読み上げられないことがあるため、要素は最初から置いておき、読み込みが終わったら文言を「記事を読み込みました」のように書き換えるとよい。