:nth-child と :nth-of-type から DOM について思いを馳せた

最近まで :nth-child:nth-of-type の違いについて知らなかった. というか,:nth-of-type はどこかで見たことある〜程度で使ったことがなかった. 知らなくても CSS なんとなく大丈夫でしょ! みたいな気分で生きてきた.

だけど

この間,動的に追加されたスタイルの :nth-child が自分の想像した感じで要素にあたってなくて, なぜだろうってなったので調査する必要が出てきたところから, 昔読んだ DOM についての記事につながりだした.

表があったとして:

<table class="container">
  <tr id="some-tr">
    <th>A</th>
    <td>B</td>
    <td>C</td>
    <td>D</td>
    <th>
      <table>
        <tr>
          <td>E</td>
        </tr>
      </table>
    </th>
  </tr>
</table>

f:id:mangano-ito:20200306115504p:plain

<td> の 1, 2, 3 番目で色をそれぞれ変えたいみたいな思いがあった:

f:id:mangano-ito:20200306115536p:plain

:nth-child

なので,td:nth-child をざっくり使うと,こうなる:

f:id:mangano-ito:20200306115646p:plain

$colors: ('red', 'green', 'blue', );

.container #some-tr {
  @each $color in $colors {
    $index: index($colors, $color);

    & td:nth-child(#{$index}) {
      &::before { content: 'td:nth-child(#{$index})'; }
      &         { border: medium solid #{$color}; }
    }
  }
}

実際のところ,B, C, E にスタイルがあたるようになっている.

理由

これは 2 つ理由があって:

  • td:nth-child は まず :nth-child な要素があって,それを td で絞りこまれている
  • E<td> は 直近の <table> について見れば,td:nth-child(1) であるということ

なので,ぱっと見 1 番目の <td> っぽい A にはスタイルはあたらず, ぱっと見 5 番目 かつ <th>E に 1 番目のスタイルがあたるようになっている.

:nth-of-type

なので,意図した動作のためには,:nth-of-type> を使って こう書く必要がある:

$colors: ('red', 'green', 'blue', );

.container #some-tr {
  @each $color in $colors {
    $index: index($colors, $color);

    & > td:nth-of-type(#{$index}) {
      &::before { content: 'td:nth-of-type(#{$index})'; }
      &         { border: medium solid #{$color}; }
    }
  }
}

f:id:mangano-ito:20200306121025p:plain

ほか

ほかにも検証してみた

:nth-child(1) とかユニークで 1 つしか返ってこないでしょ,みたいな気分で, document.querySelector なんかした日には意図しない要素は返ってくるわ, querySelctorAll したら複数返ってくるわで意味不明なことになったのだった.

なぜ

なぜ混乱したのかというと, 自分が多くの擬似クラスは前から後ろに絞り込んでいくように思い込んでいたところに, E:nth-child:nth-childE みたいな絞り込みかたになっているからでは,と思った.

developer.mozilla.org

仕様で E:pseudo-classE:pseudo-class の順だよ,とか決まってるのかは知らないし, 確かに目的によってノードの枝刈りの仕方を変えるのは効率的そう.

たとえば p:emptry なんか DOM 上にある全部の <p> を走査してから :empty な要素を見つけるよりも, :empty な要素はたいていのケースで少なそうなので, もし empty な要素のテーブルを持っているなら, そこから走査するほうが早いかもしれない……

とか,考えついたときに,昔読んだ Qiita の記事を思い出した:

qiita.com

昔,正直 BEM ってどうなんだろう,って自分の中で思ってたが, この記事を見てなるほどってなったのだった.

今見たら更新があって,他の方の記事も紹介されていて面白い:

qiita.com

hayatoito.github.io

Chrome というか Chromium の実装から紐解いてるのが面白い.

整理すれば確かにそうだねってなったので,気をつけたい次第.