🪜 sibling-index()で「JSなしスタガード」を自サイトに入れてみた — min()で頭打ちにするのがコツだった

CSS sibling-index()を使うと、リストの連番アニメーションがHTML変更ゼロ・JSゼロで書ける。実サイトの記事一覧に導入して分かった、遅延の頭打ち・@supports・reduced-motionの実装パターンを共有する。

読む深さ

リストの項目を上から順にふわっと出す、いわゆるスタガードアニメーション。これまでの選択肢は「HTMLに style="--i: 3" を1個ずつ振る」「JSで index * 100ms を流し込む」「nth-childで要素数ぶんCSSを書く」の三択で、どれを選んでも小さな敗北感があった。

sibling-index() はこれを終わらせる関数だ。要素が兄弟の何番目か(1始まり)を返すだけ。それだけなんだけど、animation-delay に掛け算するだけで連番遅延が完成する。このサイトのトップの記事一覧に実際に入れたので、その過程で決めたことを書いておく。

実装は10行で終わった

うちのトップページは .post-list の下に .post-card が並ぶだけの構造だ。そこにこう足した。

@keyframes card-in {
  from { opacity: 0; translate: 0 12px; }
  to   { opacity: 1; translate: 0 0; }
}

.post-list > .post-card {
  animation: card-in 0.45s cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: min(calc((sibling-index() - 1) * 45ms), 500ms);
}

HTMLは1文字も触っていない。記事が増えても減っても、ブラウザが勝手に番号を振り直してくれる。ビルド時に連番を埋め込む必要すらない。

ポイントは min() で遅延を500msで頭打ちにしているところだ。最初は素直に sibling-index() * 45ms だけで書いたんですが、記事が30本あると最後のカードは1.3秒待ちになる。スクロールして初めて見える位置のカードが、見えた時点でまだ出現待ちをしているのは間抜けでしかない。ファーストビューの数枚だけ階段状に出て、それ以降はほぼ同時。これが体感として一番自然だった。

あと sibling-index() - 1 にしているのは、1始まりだから。そのまま使うと先頭カードにも45msの遅延が乗って、ページ全体がワンテンポ遅れて見える。地味だけど気づくまで数分悩んだ。

Firefoxがまだなので、@supportsで包む

対応状況は2026年7月時点でChrome 138以降・Edge・Safari 26.2。Firefoxが未対応で、Baselineには届いていない。なので全体をこう包んでいる。

@supports (animation-delay: calc(sibling-index() * 1ms)) {
  @media (prefers-reduced-motion: no-preference) {
    /* 上記のスタガード一式 */
  }
}

非対応ブラウザではブロックごと無視されて、即時表示のまま。装飾アニメーションはこの「段階的強化」がいちばん気楽だ。Firefoxユーザーには申し訳ないが、出ないことに誰も気づかない類の演出なので、フォールバックを別途書くことはしなかった。prefers-reduced-motion を重ねているのは、動きを減らす設定の人に階段演出を見せる理由がないからです。

兄弟の「総数」も取れる

対になる sibling-count() を使うと兄弟要素の総数が取れるので、位置÷総数の比率が計算できる。放射状メニューの角度(rotate(calc(sibling-index() / sibling-count() * 360deg)))や、リストの先頭から末尾へ色相を回すグラデーションみたいな、これまでSassのループかJSでしか書けなかった系の表現がCSS単体に降りてくる。

なお、この関数自体の解説はICS MEDIAやQiitaに既に良い記事がある。この記事の持ち分は「実サイトに入れたら min() の頭打ちと -1 が必要だった」という運用の皺の部分だ。

インデックスを手で振る作業は、思えばずっと「HTMLとCSSの間の隙間を人間が埋める」仕事だった。その隙間がひとつ塞がった。あなたのプロジェクトの style="--i:"、もう消せるかもしれませんよ。

元ネタ: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/sibling-index