できない.dev

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 の要素ごと後から差し込むと読み上げられないことがあるため、要素は最初から置いておき、読み込みが終わったら文言を「記事を読み込みました」のように書き換えるとよい。

この記事は役立ちましたか?