【コピペOK】CSS スケルトン ローディング 9選
css スケルトンとは、本物のコンテンツが届く前に画面の形だけを灰色の板で先に見せておくローディング表現のことです。この記事ではシマーライン・カード・表・コンテナクエリまで9種類を選び、JavaScript を使わず SCSS だけで実装しました。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 シマーライン
- 02 カードスケルトン
- 03 アバターリスト
- 04 テーブルスケルトン
- 05 画像ブラーアップ
- 06 レスポンシブスケルトン
- 07 スケルトン→本文
- 08 行ごとに時差
- 09 ダークモード自動配色
並び順は効果の派手さではなく、実際に画面を組み立てていく順番です。最初の4つ(01〜04)は行・カード・アバター・表という基本の形で、次の2つ(05〜06)は写真とレイアウト幅という「揺れやすい要素」への対処です。最後の3つ(07〜09)は、スケルトンが役目を終えて本物に切り替わる場面・長い一覧での見え方・配色の自動化という、仕上げの挙動を扱います。「css ローディングアニメーション」で探している人が知りたい細部を、項目ごとのコードに残しました。
01シマーライン
文字行の位置を灰色の棒が先取りし、::after の光の帯だけが translateX で通り過ぎます。位置ではなく transform だけを動かすのでレイアウトは揺れず、行ごとに開始をわずかにずらすと1本の光が順番に流れて見えます。記事一覧やコメント欄の読み込みに向いています。
.line {
position: relative;
overflow: hidden;
height: 14px;
border-radius: 6px;
background: rgba($color, .14);
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($color, .45), transparent);
transform: translateX(-120%);
animation: shimmer $dur-loop $ease-out infinite;
}
&:nth-child(2)::after { animation-delay: -140ms; }
}
@keyframes shimmer {
to { transform: translateX(120%); }
}
02カードスケルトン
画像・見出し・説明の3つの枠を持つカード1枚です。共通の光る帯を %bar というプレースホルダーセレクターにまとめ、.thumb・.title・.desc がそれぞれ @extend するだけで同じ動きを再利用できます。商品カードや記事カードの一覧に向いています。
%bar {
position: relative;
overflow: hidden;
border-radius: 6px;
background: rgba($color, .16);
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($stage-paper, .85), transparent);
transform: translateX(-120%);
animation: shimmer $dur-loop $ease-out infinite;
}
}
.thumb { @extend %bar; aspect-ratio: 2 / 1; }
.title { @extend %bar; height: 14px; width: 70%; }
.desc { @extend %bar; height: 11px; }
03アバターリスト
丸いアバターと2行のテキストが5回繰り返されます。01・02が行やカードごとに光る帯を持つのに対し、03は .list 全体を覆う .sheen をひとつだけ置き、リスト全体を1本の光が横切るようにしました。チャットやフォロワー一覧に向いています。
.list {
position: relative;
overflow: hidden;
display: grid;
gap: 10px;
border-radius: 12px;
}
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba($color, .22);
}
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($stage-paper, .4), transparent);
transform: translateX(-120%);
animation: shimmer $dur-loop $ease-out infinite;
pointer-events: none;
}
04テーブルスケルトン
見出し行の文字はそのまま残し、本文のセルだけを棒に置き換えます。.row は見出しと本文で同じ grid-template-columns を共有しているので、読み込み中も列の幅がずれません。ダッシュボードの表に向いています。
.row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 8px;
padding: 8px 12px;
}
.body {
position: relative;
overflow: hidden;
}
.cell {
height: 10px;
border-radius: 6px;
background: rgba($color, .13);
}
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($subject-blue, .45), transparent);
transform: translateX(-120%);
animation: shimmer $dur-loop $ease-out infinite;
}
05画像ブラーアップ
極小サイズの画像を blur(20px) で先に敷いておき、本物の画像がその上でふわっと現れて鮮明になります。イージングを ease 系ではなくあえて linear にしたのは、途中で速度が変わると「ぼけている時間」が不揃いに見えたためです。ヒーロー画像やギャラリーに向いています。
.placeholder {
filter: blur(20px);
transform: scale(1.15);
}
.real {
opacity: 0;
transform: scale(1.04);
animation: reveal $dur-loop linear infinite;
}
@keyframes reveal {
0% { opacity: 0; transform: scale(1.04); }
50% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(1.04); }
}
06レスポンシブスケルトン
入れ物の実際の幅に応じて列数が変わります。ビューポート幅ではなく .wrap 自身の幅を基準にする container-type: inline-size を使うので、同じファイルをサイドバーに置いても本文に置いても正しい列数になります。コンテナクエリは Chrome 105 以降・Safari 16 以降・Firefox 110 以降が対応済みで、対応状況は MDN の @container 対応表 で確認できます。
.wrap {
container-type: inline-size;
container-name: skeleton;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
@container skeleton (max-width: 260px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@container skeleton (max-width: 150px) {
.grid { grid-template-columns: 1fr; }
}
07スケルトン→本文
棒が跳ねずに実際の内容へ切り替わります。.skeleton と .content を同じ grid-area: 1 / 1 に重ね、opacity だけをクロスフェードさせるので、幅や高さが変わって周りの要素が飛ぶことがありません。すべてのスケルトンの最後の場面として使えます。
.card {
width: 74%;
display: grid;
}
.skeleton, .content {
grid-area: 1 / 1;
}
.skeleton {
animation: fade-out $dur-loop $ease-out infinite;
}
@keyframes fade-out {
0%, 40% { opacity: 1; }
55%, 85% { opacity: 0; }
100% { opacity: 1; }
}
08行ごとに時差
各行が少しずつ遅れて光り、波のように見えます。Sass の @for で行ごとの animation-delay を自動計算し、偶数行だけ animation-name を逆方向のキーフレームに差し替えているので、波が途中で分かれて見える効果を1つのルールで作れます。長い一覧に向いています。
.bar::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($subject-blue, .35), transparent);
transform: translateX(-120%);
animation: shimmer $dur-loop $ease-out infinite;
}
@for $i from 1 through 6 {
.bar:nth-child(#{$i})::after { animation-delay: -($i - 1) * 60ms; }
}
.bar:nth-child(even)::after { animation-name: shimmer-reverse; }
@keyframes shimmer-reverse {
from { transform: translateX(120%); }
to { transform: translateX(-120%); }
}
09ダークモード自動配色
light-dark() ひとつで、ダークモードのときに色が自動で切り替わります。color-scheme: light dark を宣言した要素の中で使うと、ライト用とダーク用の2つの値のうちどちらを描くかをブラウザ自身が判断してくれるので、prefers-color-scheme の分岐を自分で書く必要がありません。ダークモードを持つすべてのサイトに向いています。
:root {
color-scheme: light dark;
}
.card {
background: light-dark(#f5f2e9, #1b1b1f);
color: light-dark(#1b1b1f, #f5f2e9);
}
.bar {
background: light-dark(rgba(#1b1b1f, .16), rgba(#f5f2e9, .22));
}
どこで崩れるか — 落とし穴
9つとも overflow: hidden を持つ親要素の中でだけ光の帯を動かしています。01を作ったとき、この overflow: hidden を忘れると光の帯がカードの外側にまではみ出し、隣の要素の上を横切って見えるという崩れ方をしました。position: relative の親に overflow: hidden を必ずセットで書くのが、シマー系5つ(01・02・03・04・08)に共通する条件です。
もうひとつは06のコンテナクエリです。デモでは仕組みを見せるためにあえて .wrap の width を keyframes で動かしていますが、これは「揺れやすい要素」を可視化するための例外で、コンテナクエリ自体は transform の変化には反応しません。実際のページでは、サイドバーの開閉やウィンドウのリサイズのような本物のボックス幅の変化に対して .grid が反応します。修正済みの9ファイルを開くには a3rjf9rd という文字列が必要です。下のカードから zip を取ってください。
3つ目はブラウザの対応です。light-dark() と @container はどちらも比較的新しい構文で、09の light-dark() は Chrome・Edge 123 以降と Safari 17.5 以降が対応済みですが、MDN の light-dark() 対応表 にあるとおり古いブラウザでは値そのものが無効になり、背景色が透明のまま抜けてしまいます。zip のファイルは @supports で分岐させ、非対応ブラウザでは固定の色にフォールバックするようにしてあります。
アクセシビリティ
9つとも prefers-reduced-motion: reduce で光の移動や拡大縮小の animation を止めます。ただし止めた後に何を残すかは項目ごとに違い、01・02・03・04・08は光を止めて薄い一定の明るさだけを残し、05は最も鮮明な最終状態を、07は本文が見えている状態を、09はダークモードのカードを固定で表示します。06だけは動きを止めても列数の変化そのものが機能なので、width のアニメーションだけを止め、コンテナクエリの判定はそのまま生かしています。
@media (prefers-reduced-motion: reduce) {
.line::after, .sheen { animation: none !important; opacity: .4; }
.real { animation: none !important; opacity: 1; transform: scale(1); }
}
スケルトン以外の CSS アニメーションは CSS アニメーションのカテゴリ に、このサイトについては 紹介ページ にまとめています。
FAQ
スケルトンスクリーン css とローディングスピナー、どちらを使うべきですか?
読み込むものの形があらかじめ分かっているなら、スケルトンスクリーンのほうが向いています。カードや表のように最終的な形が決まっている場所ではスケルトンが「もうすぐこの形の内容が来る」と伝えられますが、形が定まらない処理中の待ち時間にはスピナーのほうが誤解を招きません。
9つを1つのページに全部入れても大丈夫ですか?
入れられますが、06のコンテナクエリだけは実際のレイアウトで使うときに親のボックス幅がはっきり決まっている場所に置いてください。幅が決まっていない場所に置くと、列数の切り替わる境目が意図しない位置にずれることがあります。それ以外の8つは1つのページに何個置いても互いに影響しません。
React ではどう書きますか?
zip の react/ フォルダのコンポーネントは、状態管理をせずクラス名を1つ付けるだけで9つとも同じ見た目になります。09の light-dark() だけは CSS 側で完結しているため、ダークモード用の状態を React 側に持つ必要がありません。