スケルトンスクリーンとは

これから表示されるコンテンツの形を、グレーのブロックで先に見せておく読み込み中の表現。

概要

これから表示される画像や文章の「形」だけを、グレーのブロックであらかじめ見せておく表現です。 光る帯が左から右へ流れるアニメーション(シマー)を付けることが多く、まだ何もない画面よりも早く表示が始まったように感じさせ、待ち時間のストレスを減らせます。実際のデータが届いたら、ブロックを本物のコンテンツに置き換えます。

別名

表記ゆれ:スケルトンローディング、プレースホルダー UI、skeleton screen

骨組みだけが先に見えることから「スケルトン(骨格)」と呼ばれます。

AI への伝え方

「読み込み中はスケルトンスクリーンにして」で通じます。 カードや一覧の形に合わせてブロックの数・配置を伝え、シマーのアニメーションを付けるかどうかも仕様として伝えると実装がぶれません。

似たパーツとの違い

「ローディングスピナー」は処理中であることだけを伝えるのに対し、スケルトンスクリーンは完成後のレイアウトの形まで先に見せる違いがあります。 「プログレスバー」は進捗の割合を数値やバーの長さで伝えるのに対し、スケルトンスクリーンは割合を出さずに「もうすぐ表示される」ことだけを伝えます。

デモ

コード

HTML
<div class="skeleton-screen-demo">
  <button class="skeleton-screen-demo__toggle" type="button" data-skeleton-toggle>
    読み込み完了にする
  </button>

  <div class="skeleton-screen-demo__card">
    <div class="skeleton-screen-demo__skeleton" data-skeleton-state>
      <span class="skeleton-screen-demo__block skeleton-screen-demo__block--thumb"></span>
      <span class="skeleton-screen-demo__block skeleton-screen-demo__block--title"></span>
      <span class="skeleton-screen-demo__block skeleton-screen-demo__block--text"></span>
      <span class="skeleton-screen-demo__block skeleton-screen-demo__block--text"></span>
    </div>

    <div class="skeleton-screen-demo__real" data-real-state hidden>
      <div class="skeleton-screen-demo__thumb-real" aria-hidden="true"></div>
      <p class="skeleton-screen-demo__title-real">読み込みが完了しました</p>
      <p class="skeleton-screen-demo__text-real">実際のコンテンツは、このようにブロックと入れ替わって表示されます。</p>
    </div>
  </div>
</div>
CSS
.skeleton-screen-demo {
  font-family: sans-serif;
}

.skeleton-screen-demo__toggle {
  margin-bottom: 16px;
  padding: 8px 16px;
  border: 1px solid #999;
  border-radius: 6px;
  background: #fff;
  font-size: 13px;
  cursor: pointer;
}

.skeleton-screen-demo__card {
  width: 100%;
  max-width: 280px;
  padding: 16px;
  border: 1px solid #e6e6e6;
  border-radius: 12px;
  background: #fff;
}

/* hidden 属性の既定スタイル(display: none)を上書きしないよう display は指定しない */
.skeleton-screen-demo__skeleton[hidden] {
  display: none;
}

/* hidden 属性の既定スタイル(display: none)を保つため、display は個別に指定しない */
.skeleton-screen-demo__block {
  display: block;
  border-radius: 6px;
  background: linear-gradient(90deg, #e6e6e6 25%, #f2f2f2 37%, #e6e6e6 63%);
  background-size: 400% 100%;
  animation: skeleton-screen-demo-shimmer 1.4s ease infinite;
}

.skeleton-screen-demo__block--thumb {
  width: 100%;
  height: 120px;
  margin-bottom: 12px;
}

.skeleton-screen-demo__block--title {
  width: 60%;
  height: 14px;
  margin-bottom: 10px;
}

.skeleton-screen-demo__block--text {
  width: 100%;
  height: 10px;
  margin-bottom: 8px;
}

@keyframes skeleton-screen-demo-shimmer {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}

.skeleton-screen-demo__thumb-real {
  width: 100%;
  height: 120px;
  margin-bottom: 12px;
  border-radius: 6px;
  background: #0017c1;
}

.skeleton-screen-demo__title-real {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: bold;
  color: #333;
}

.skeleton-screen-demo__text-real {
  margin: 0;
  font-size: 13px;
  color: #666;
  line-height: 1.6;
}
JS
const toggle = document.querySelector("[data-skeleton-toggle]");
const skeleton = document.querySelector("[data-skeleton-state]");
const real = document.querySelector("[data-real-state]");
let loaded = false;

toggle.addEventListener("click", () => {
  loaded = !loaded;
  skeleton.hidden = loaded;
  real.hidden = !loaded;
  toggle.textContent = loaded ? "読み込み前に戻す" : "読み込み完了にする";
});