かわいい鹿のイラスト

兄弟要素のインデックスを返す CSS 関数 sibling-index()

`sibling-index()` は要素の兄弟要素の中でのインデックスを返します。`sibling-index()` 関数により取得したインデックスを使用することにより、スタッガー(時間差)アニメーションや、色相を段階的に変えるといった、兄弟要素の位置に基づいたスタイリングが可能になります。これまでは JavaScript を使用して実装する必要があったような効果も、純粋な CSS で実現できるようになります。

sibling-index() は CSS Values and Units Module Level 5 で定義された関数で、要素の兄弟要素の中でのインデックスを返します。インデックスは 1 から始まり、同じ親を持つ要素の中で、現在の要素が何番目に位置しているかを示します。例えば、ある要素が親の子要素の中で 3 番目に位置している場合、sibling-index() は 3 を返します。

li {
  /* 3 番目の要素なら 3 * 50px = 150px の幅にする */
  width: calc(sibling-index() * 50px);
  background-color: lawngreen;
}

sibling-index() 関数により取得したインデックスを使用することにより、スタッガー(時間差)アニメーションや、色相を段階的に変えるといった、兄弟要素の位置に基づいたスタイリングが可能になります。これまでは JavaScript を使用して実装する必要があったような効果も、純粋な CSS で実現できるようになります。

sibling-index() 関数が提案される以前でも :nth-child():nth-of-type() といった擬似クラスを使用して、兄弟要素の位置に基づいたスタイリングは可能でした。しかし、これらの擬似クラスは特定のパターンにマッチする要素を選択するためのものであり、要素自身が自分のインデックスを知ることはできませんでした。インデックスを取得したい場合には以下のような冗長なコードが必要でした。

li {
  width: calc((var(--index) + 1) * 50px);
  background-color: lawngreen;
}
 
/* 要素の数だけ同様のコードが続く... */
li:nth-child(1) {
  --index: 0;
}
li:nth-child(2) {
  --index: 1;
}
li:nth-child(3) {
  --index: 2;
}

インデックスや個数を計算に使いたいというニーズは継続的に存在していましたが、これまでは CSS だけで完結させることができませんでした。sibling-index() 関数の追加により、これらのニーズを CSS だけで満たすことができるようになります。この記事では sibling-index() 関数を使用したスタイリングの例をいくつか紹介します。

スタッガー(時間差)アニメーション

スタッガーアニメーションは、同一の要素に対してアニメーションの開始時間をずらすことで、波のような動きを作り出すテクニックです。animation-delay の計算に sibling-index() を使用することで、兄弟要素の位置に応じてアニメーションの開始時間をずらすことができます。以下のコード例では、リストアイテムが順番にフェードインするスタッガーアニメーションを実装しています。

li {
  opacity: 0;
  transform: translateX(-12px);
  animation: slide-in 0.5s ease-out forwards;
  /* 1番目=120ms, 2番目=240ms, ... */
  animation-delay: calc(sibling-index() * 120ms);
}
 
@keyframes slide-in {
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

色相を段階的に変える

sibling-index()sibling-count() を組み合わせることで、色相を段階的に変えるといったスタイリングも可能になります。sibling-count() は同じ親を持つ要素の総数を返す関数です。sibling-index() で取得したインデックスを角度(hue)に変換し、oklch() 関数の色相に渡すことで、実現しています。sibling-count() と組み合わせれば、要素が何個でも開始 hue から終了 hue まで均等に色相が変化するようにできます。

li {
  --start: 200;
  --end: 320;
  background: oklch(
    65% 0.15
      calc(
        var(--start) + (var(--end) - var(--start)) / (sibling-count() - 1) *
          (sibling-index() - 1)
      )
  );
}

扇形の配置

sibling-index() を使用して、要素の位置に応じた角度を計算し、transform: rotate() で回転させることで、扇形に要素を配置できます。扇形の配置に必要なのは、各要素を中心点を基準に異なる角度で回転させることです。扇全体の広がり(最大 360 度)を --spread として定義し、要素の数に応じてステップ角度を計算します。各要素の最終的な角度は、ステップ角度とインデックスから計算されます。

.card {
  --spread: 60deg; /* 扇全体の広がり */
  --radius: 240px; /* 扇の半径 */
 
  /* 1 枚あたりのステップ角度 */
  --step: calc(var(--spread) / (sibling-count() - 1));
 
  /* 各カードの最終的な角度 (中央=0deg) */
  --angle: calc(var(--step) * (sibling-index() - 1) - var(--spread) / 2);
 
  /* カードを回転させる */
  transform: rotate(var(--angle)) translateY(calc(var(--radius) * -1));
  transform-origin: bottom center;
}

円形に出現するメニュー項目

フローティングアクションボタンでよく見る演出で、ボタンにカーソルを合わせると、複数のメニュー項目が円形に出現するというものがあります。これも sibling-index() を使用して、要素の位置に応じた角度を計算し、transform: rotate() で回転させることで実装できます。扇形の配置と同様に、各要素を中心点を基準に異なる角度で回転させることがポイントです。

/* 円周上に並ぶメニュー項目 */
.menu .item {
  /* 1 始まりの index を 0 始まりに揃える */
  --i: calc(sibling-index() - 1);
 
  /* 中央のトリガーは数えたくないので - 1 で調整 */
  --total: calc(sibling-count() - 1);
 
  /* 円周を均等分割した角度 */
  --angle: calc(360deg / var(--total) * var(--i));
 
  /* 半径とアニメーション速度 */
  --radius: 110px;
  --delay-step: 70ms;
 
  /* 初期状態はすべて重なっていて透明 */
  transform: rotate(var(--angle)) translateY(0) rotate(calc(var(--angle) * -1));
  opacity: 0;
 
  /* index に応じてアニメーションの開始時間をずらす */
  transition:
    transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)
      calc(var(--i) * var(--delay-step)),
    opacity 0.3s ease-out calc(var(--i) * var(--delay-step));
}
 
/* トリガーにホバーしたとき、メニュー項目を円形に展開 */
.menu:hover .item {
  /* index に応じた角度で回転させて、半径分だけ移動させる */
  transform: rotate(var(--angle)) translateY(calc(var(--radius) * -1))
    rotate(calc(var(--angle) * -1));
  opacity: 1;
}

まとめ

  • sibling-index() は要素の兄弟要素の中でのインデックスを返す CSS 関数
  • sibling-count() は同じ親を持つ要素の総数を返す CSS 関数
  • これらの関数を使用することで、スタッガーアニメーションや色相の段階的な変化、扇形の配置など、兄弟要素の位置に基づいたスタイリングが可能になる

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

`sibling-index()` が返す値として、記事で説明されているものはどれですか?

  • 同じ親を持つ要素の総数

    もう一度考えてみましょう

    同じ親を持つ要素の総数を返すのは `sibling-count()` です。`sibling-index()` は現在の要素の位置を返します。

  • 現在の要素が兄弟要素の中で何番目に位置しているかを示すインデックス

    正解!

    記事では、`sibling-index()` は要素の兄弟要素の中でのインデックスを返し、インデックスは 1 から始まると説明されています。

  • 現在の要素に指定された CSS カスタムプロパティの値

    もう一度考えてみましょう

    記事では、従来はカスタムプロパティでインデックスを持たせる必要があったと説明されていますが、`sibling-index()` が返す値ではありません。

  • 親要素から見た子孫要素全体の階層の深さ

    もう一度考えてみましょう

    記事では階層の深さを返す関数としては説明されていません。対象は同じ親を持つ兄弟要素の中での位置です。