LoginSignup
6
2

More than 3 years have passed since last update.

CSS の nth-child と variable で、要素の順番に応じた変化をつける方法

Posted at

100個の要素の順番を、21個のセレクタで特定して変化をつけたcodepenのデモ

See the Pen Use nth-child as CSS variable by keisuke Takahashi (@ksksoft) on CodePen.


要素の順番を取得するならcounterでは?

要素の順番はCSSのcounter で取得できます。カスタマイズしてリストマーカー的に使ったりするアレです。
しかし、counter で取得した内容はテキストなので、擬似要素に出力はできても、variableでcalc() できません。つまり、計算には使えない残念さんです。


要素数が少なければ、順番毎に処理を書いてしまえ

3つの要素に順番に変化をつけたい場合、nth-child()を3つ書けばいいです。

html
 <ul>
  <li>one</li>
  <li>two</li>
  <li>three</li>
 </ul>
css
li {
  background-color: pink;
  list-style: none;
  width: 10em;
  animation: roll infinite 10s 0s both;
}

/* 開始から3秒は停止、2秒使って1回転、その後停止させるkeyframes */
@keyframes roll {
  0%,
  30% {
    transform: rotate(0turn);
  }
  50%,
  100% {
    transform: rotate(1turn);
  }
}

/* 同じkeyframesを使い、要素の順番に応じてanimation-delayだけを変える */
li:nht-child(1) { animation-delay: 0s;}
li:nht-child(2) { animation-delay: 1s;}
li:nht-child(3) { animation-delay: 2s;}

keyframesは使い回す

同じ動きなら同じkeyframes を使う、というのがCSSanimation のコツです。keyframes の%を要素の順番で計算してずらす、なんてことはやらないように
まずkeyframes を要素の数だけ書くのは大変ですし、animation の動きを変えたくなった時に、全てのkeyframes の%を調整するハメになります。


でも、要素数が100個ならどうする?

:nth-child(1){} から:nth-child(100){} まで書けばできます。
できますが、:nth-child(An + B){} のようにn変数を使って記述したいところです。


要素の順番を特定する機能を切り離す

codepenで1から100までを特定した部分を抜粋します(ここがポイントです)。

css
  /* Get element order */
  li:nth-child(10n + 0) { --digit-1: 0; }
  li:nth-child(10n + 1) { --digit-1: 1; }
  li:nth-child(10n + 2) { --digit-1: 2; }
  li:nth-child(10n + 3) { --digit-1: 3; }
  li:nth-child(10n + 4) { --digit-1: 4; }
  li:nth-child(10n + 5) { --digit-1: 5; }
  li:nth-child(10n + 6) { --digit-1: 6; }
  li:nth-child(10n + 7) { --digit-1: 7; }
  li:nth-child(10n + 8) { --digit-1: 8; }
  li:nth-child(10n + 9) { --digit-1: 9; }

  li:nth-child(-n + 10) { --digit-2: 0; }
  li:nth-child(n + 10):nth-child(-n + 20) { --digit-2: 10; }
  li:nth-child(n + 20):nth-child(-n + 30) { --digit-2: 20; }
  li:nth-child(n + 30):nth-child(-n + 40) { --digit-2: 30; }
  li:nth-child(n + 40):nth-child(-n + 50) { --digit-2: 40; }
  li:nth-child(n + 50):nth-child(-n + 60) { --digit-2: 50; }
  li:nth-child(n + 60):nth-child(-n + 70) { --digit-2: 60; }
  li:nth-child(n + 70):nth-child(-n + 80) { --digit-2: 70; }
  li:nth-child(n + 80):nth-child(-n + 90) { --digit-2: 80; }
  li:nth-child(n + 90):nth-child(-n + 100) { --digit-2: 90; }
  li:nth-child(n + 100):nth-child(-n + 110) { --digit-2: 100; }

途中の改行を挟んで、前半と後半に分けて説明します。


【前半】要素の順番の1の位を特定する

前半の10個のセレクタで、要素の順番の1の位を特定します。
:nth-child(An){}は、「A個ごとに〜」という意味になります。順番は、10進数で取得したいので、Aに10を入れます。
:nth-child(10n + B){} とすれば、「10個ごとにBを足す」というセレクタになります。
例えば、:nth-child(10n + 1){} にマッチする要素の順番は、

n 代入して計算 結果
0 (10 * 0 + 1) 1
1 (10 * 1 + 1) 11
2 (10 * 2 + 1) 21
3 (10 * 3 + 1) 31
--- --- ---
9 (10 * 9 + 1) 91

となり、要素の順番の1の位が全て1になる、という仕組みです。
こうして取得した1の位を、--digit-1と定義したvariableの値として代入できます。


【後半】要素の順番の10の位を特定する

後半で、要素の順番の10の位を特定します。ここで使用するnth-childは2種類あります。

セレクタ 意味
:nth-child(-n + C) C以下にマッチ
:nth-child(n + D):nth-child(-n + C) C〜Dの範囲にマッチ

したがって、:nth-child(-n + 10){}は、10以下の要素にマッチします。
1から10番目の要素では、10の位として--digit-2と定義したvariableが、0になります。

次の :nth-child(n + 10):nth-child(-n + 20) {} は、10〜20の要素にマッチして、--digit-2に10の位として 10 が代入されます。
ちなみに、10番目の要素が、直前のセレクタと重複してますが、:nth-childが2回使われているので、その分、セレクタの詳細度が上がり、こちらが優先されます。

以下、20〜29には20が、30〜39には30が代入されていき、10の位が99まで特定されます。各セレクタの数字を10刻みにしているのは、可読性をあげて理解しやすくするためです。

最後のセレクタは100番目の要素になり、10の位は100になります。
ここを、3桁目にして、--digit-3とするのが正しいでしょうが、今回は1から100までを特定するということで省略しました。


var()に要素の順番から計算した変数を使えば、記述は1回で済む

再び、codepenから抜粋します。

css
li {
  /* ここまでは省略。以下が肝要。 */
  --digit-1: 0;
  --digit-2: 0;
  --h: calc(var(--digit-1) + var(--digit-2));
  --delay: calc( (var(--digit-1) + var(--digit-2)) * 0.1s);
  background-color: hsla(var(--h), 100%, 60%, 0.9);
  animation: roll infinite 10s var(--delay) both;
}

念の為、--digit-1--digit-2に0を代入しています。
--hが要素の「順番」です。--digit-1--digit-2calc()で足して--hに代入しています。
--delayは その順番に0.1秒を掛けた値です。もちろん、直前に算出した--hに0.1秒を掛けても良いです。


要素順に応じて変化させる

codepenのデモでは、背景色とアニメーションの開始するタイミングを変化させてみました。
background-colorhsla()hの部分に、var(--h)を使い、色相を変化させます。
animationの指定では、1つ目の秒数がanimation-durationになり、2つ目の秒数が animation-delayになる決まりなので、2つ目にvar(--delay)を指定します。

これで、要素数が未定でも、変化がつけ易くなります。


記述量は減らせたか?

要素の順番毎にセレクタと処理を書いて、変化を実装する場合、要素が多くなると、数に比例して記述量が増えてしまいます。

順番毎に処理を書く場合

要素数 セレクタの数 varの数
10 10 ---
100 100 ---
1000 1000 ---

順番を特定する部分を分離した場合

要素数 セレクタの数 varの数
10 11 1
100 21 2
1000 41 3

特定したい要素数が増えるほど、恩恵にあずかれる、という主張です。

処理は一回だけ書けば良い

特定した順番を変数として利用すれば、一回だけ処理を書けば良いことになります。要素の順番毎にセレクタと処理を書く必要はありません。記述もスッキリします。


おわりに

世界でなら500人、日本でも30人くらいに、この投稿やcodepenが刺さると良いなあ、と思います。
Qiitaへの投稿は初です。無作法があったらすいません。

CSSメタ言語を使えばイイじゃん、と思ったあなたへ

LessとかSassとかでmixinとかforとかするのかもしれませんが、私はメタ言語は使わないので、パスします。
1000個のセレクタを簡単に書けそうですが、コンパイル後の行数は増えちゃう気がしますが、どうなんでしょう?
CSSメタ言語での記述がお分かりになる方は、コメントやご自身の投稿で、その方法を補足してください。

javascriptを使えばイイじゃん、と思ったあなたへ

CSSだけでやるのが面白いし、好きなんだよ、という主張なんです。

6
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2