No.193
LOADING CSSJS

Sibling Index Spinner

円周に並んだ点が順番に明るくなるスピナーを、CSS の sibling-index() と sibling-count() だけで配置。点を増やしても減らしても、角度と時間差が自動で割り振られ、nth-child を何行も書く必要がありません。

MY COLLECTION RANDOM ↗

DEMO

点 8 個

CODE

HTML
<div class="demo-stage sbi-stage">
  <div class="sbi-ring" id="sbi-ring" role="img" aria-label="読み込み中">
    <span class="sbi-dot"></span><span class="sbi-dot"></span><span class="sbi-dot"></span><span class="sbi-dot"></span>
    <span class="sbi-dot"></span><span class="sbi-dot"></span><span class="sbi-dot"></span><span class="sbi-dot"></span>
  </div>
  <div class="sbi-ctrl">
    <button type="button" class="sbi-btn" id="sbi-minus" aria-label="点を減らす">−</button>
    <output class="sbi-count" id="sbi-count" aria-live="polite">点 8 個</output>
    <button type="button" class="sbi-btn" id="sbi-plus" aria-label="点を増やす">+</button>
  </div>
</div>
CSS
.sbi-stage { position:relative; overflow:hidden; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1.4rem; padding:2rem 1.25rem; background:#0B0B10; min-height:300px; }
.sbi-ring { position:relative; width:120px; height:120px; }
.sbi-dot {
  /* 何番目か(1から)と兄弟の数。未対応ブラウザでは JavaScript が同じ名前で上書きする */
  --i: sibling-index();
  --n: sibling-count();
  --a: calc(360deg / var(--n) * (var(--i) - 1));   /* 自分の角度 */
  position:absolute; width:12px; height:12px; margin:-6px 0 0 -6px; border-radius:50%;
  background:#F472B6;
  /* sin() と cos() で円周上の位置を出す(scale のアニメーションで半径が変わらない) */
  left: calc(50% + sin(var(--a)) * 48px);
  top: calc(50% - cos(var(--a)) * 48px);
  animation: sbi-fade 1.2s linear infinite;
  animation-delay: calc(1.2s / var(--n) * (var(--i) - 1) - 1.2s);
}
@keyframes sbi-fade { 0% { opacity:1; scale:1.25; } 100% { opacity:.18; scale:.7; } }
.sbi-ctrl { display:flex; align-items:center; gap:.8rem; }
.sbi-btn { width:44px; height:44px; border-radius:50%; border:1px solid rgba(255,255,255,.22); background:transparent; color:#EDEDED; font-size:1.2rem; cursor:pointer; }
.sbi-btn[aria-disabled="true"] { opacity:.35; cursor:default; }   /* disabled にするとフォーカスが外れるので aria-disabled にする */
.sbi-btn:focus-visible { outline:2px solid #fff; outline-offset:3px; }
.sbi-count { min-width:5.5em; text-align:center; color:#C9C9D1; font-size:.84rem; font-variant-numeric:tabular-nums; }
@media (prefers-reduced-motion: reduce) {
  .sbi-dot { animation:none; opacity:calc(1 - (var(--i) - 1) / var(--n) * .8); }
}
JS
(function(){
  var ring = document.getElementById('sbi-ring');
  var minus = document.getElementById('sbi-minus');
  var plus = document.getElementById('sbi-plus');
  var count = document.getElementById('sbi-count');
  if (!ring || !minus || !plus || !count) return;
  var native = window.CSS && CSS.supports && CSS.supports('width', 'calc(sibling-index() * 1px)');
  var MIN = 4, MAX = 16;
  function sync(){
    var dots = ring.children, n = dots.length;
    if (!native) {   /* 未対応のブラウザだけ、番号と数をインラインで渡す */
      for (var i = 0; i < n; i++) { dots[i].style.setProperty('--i', i + 1); dots[i].style.setProperty('--n', n); }
    }
    count.textContent = '点 ' + n + ' 個';
    minus.setAttribute('aria-disabled', String(n <= MIN)); plus.setAttribute('aria-disabled', String(n >= MAX));
  }
  plus.addEventListener('click', function(){
    if (ring.children.length >= MAX) return;
    var d = document.createElement('span'); d.className = 'sbi-dot'; ring.appendChild(d); sync();
  });
  minus.addEventListener('click', function(){
    if (ring.children.length <= MIN) return;
    ring.lastElementChild.remove(); sync();
  });
  sync();
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
円周に並んだ点が順番に明るくなるローディングのスピナーを、CSS の sibling-index() と sibling-count() で作って。幅と高さ120px の枠(position:relative・role="img"・aria-label="読み込み中")に span の点を8個入れる。各点に --i: sibling-index()(1から始まる自分の番号)と --n: sibling-count()(兄弟の数)を定義し、角度 --a: calc(360deg / var(--n) * (var(--i) - 1)) を出し、left: calc(50% + sin(var(--a)) * 48px)・top: calc(50% - cos(var(--a)) * 48px) で円周に並べる(transform の rotate と translateY で並べると、アニメーションの scale が translateY の内側に掛かって点が半径方向に動いてしまうため、位置は top/left で決める)。@keyframes で opacity 1・scale 1.25 から opacity .18・scale .7 へ 1.2秒で変わるアニメーションを繰り返し、animation-delay を calc(1.2s / var(--n) * (var(--i) - 1) - 1.2s) にして、番号順に光が回るように見せる(負の遅延で最初から途中の状態で始まる)。点を増やす・減らすボタン(4〜16個・端では disabled ではなく aria-disabled="true" にして薄く表示し、押しても何もしない=disabled にするとキーボードのフォーカスが外れるため)と、「点 n 個」の表示を付け、点の数が変わっても CSS は書き換えずに角度と時間差が自動で揃うことを見せる。CSS.supports('width','calc(sibling-index() * 1px)') が false のブラウザだけ、JavaScript で各点に --i と --n をインラインで設定する。prefers-reduced-motion: reduce の時は回さず、番号に応じた濃さの固定表示にする。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 マイクロインタラクション ―UI/UXデザインの神が宿る細部 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

CREATOR TOOLS · スクール(PR)

🎬 Adobe Creative Cloud — After Effects / Photoshop 業界標準ツール → 💻 SkillHacks — プログラミング・動画編集オンラインスクール → 🚀 デイトラ — Web制作スクール・実案件レベルまで習得 →

※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。

RELATED EFFECTS

↗ MORE
→ SPINNER → DOTS PULSE → TYPING INDICATOR

sibling-index()(兄弟の中での順番を返す関数)とは

sibling-index() は、要素が兄弟の中で何番目かを整数で返す CSS の関数です。MDN では「DOM ツリーの中で、全ての兄弟要素に対する現在の要素の位置を表す整数を返す」と説明され、:nth-child() と同じく1から数えます。兄弟の数を返す sibling-count() と組み合わせると、順番に応じた角度・時間差・色を1つのルールで割り振れます。

当サイトの Spinner は回転するローディングのスピナー2種、Dots Pulse は3つの点が順に脈打つ表示、Typing Indicator はチャットで相手の入力中を示す3つの点です。本ページは点の配置と時間差を、点の数に合わせて CSS が自動で計算する別テーマです。

順番に現れるリスト、段ごとに色が変わるグラフの棒、放射状に並ぶメニューのように、これまで nth-child を何行も書くか、JavaScript で番号を style に書き込んでいた場面で使えます。項目の数がデータによって変わる部品ほど、CSS を書き直さなくて済む利点が大きくなります。

よくある質問

nth-child で書く方法と何が違いますか?

nth-child で角度や遅延を付けると、点の数だけ「:nth-child(1) { … } :nth-child(2) { … }」を書く必要があり、数を変えるたびに CSS も書き直しになります。MDN の説明では、sibling-index() は兄弟の中での自分の位置を1から始まる整数で返し、sibling-count() は兄弟の数を返します。これを calc() に入れると1つのルールで全ての点に違う値が付き、点を増やしても CSS はそのままで済みます。

未対応のブラウザではどうなりますか?

MDN の互換性データ(browser-compat-data 2026-09-24 版)では、sibling-index() と sibling-count() は Chrome・Edge 138、Firefox 154、Safari 26.2 から使えます。未対応のブラウザでは --i と --n が計算できず、全ての点が同じ位置に重なってしまうため、本実装は CSS.supports で対応を調べ、未対応の時だけ JavaScript で各点に番号と数をインラインで渡しています。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。