【テクニカル・上級編】 :first-of-type, :last-of-type, :only-of-type – CSS実践ガイド

「型」の罠を越えて:`:first-of-type` 兄弟たちの真の挙動を解剖する

フロントエンドの戦場において、CSSの擬似クラスは単なる「装飾のためのツール」ではない。ブラウザのレンダリングエンジン(BlinkやWebKitなど)がDOMツリーを走査する際、いかに効率的にノードを特定し、スタイルを適用させるか。その裏側に潜む「計算コスト」まで意識できて初めて、上級エンジニアの土俵に立てる。

今日は、一見便利だが、深く理解していないと痛い目を見る `:first-of-type`, `:last-of-type`, `:only-of-type` の深淵に潜ろう。

なぜ `:first-child` ではなく `:first-of-type` なのか?

多くのジュニア層は「最初の要素」を取りたければ `:first-child` を使う。しかし、実務においてDOM構造は常に揺らぐ。CMSから出力されるHTMLや、動的に注入されるコンポーネントにおいて、親要素の直下に意図しないノード(例えば、データバインディング用のコメントノードや、突然挿入された `` ラッパーなど)が紛れ込むことは日常茶飯事だ。

`:first-child` は「親の最初の子」という位置に固執するが、`:first-of-type` は「その型の兄弟の中で最初」という型(Tag/Type)に注目する。これにより、マークアップの微細なノイズに左右されない、極めて堅牢なセレクタを構築できる。

レンダリングエンジンとパフォーマンスの裏側

ブラウザのレンダリングパイプラインにおいて、セレクタの評価は「右から左」へ行われる。`:first-of-type` を使用すると、ブラウザは親要素内の特定型ノードをカウントする必要がある。

ここで注意すべきは再計算のコストだ。
動的にDOMが書き換わるSPA(ReactやVueなど)において、大量の兄弟ノードを持つ親要素に対し、この擬似クラスを多用すると、ブラウザは再描画のたびにノードの型を確認し続けなければならない。微々たるものに見えるが、数千ノードを扱うデータテーブルなどでこれを乱用すれば、スクロール時のカクつき(Jank)を誘発する一因となる。

/ 堅牢なCSS設計の例:特定のコンテキスト内でのみ限定的に使用する /
.data-table__row {
/ テーブルのセルの型が混在する場合でも、最初と最後の特定を確実に行う /
& > .cell:first-of-type {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}

& > .cell:last-of-type {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}

「唯一の存在」という条件式:`:only-of-type` の破壊力

`:only-of-type` は、その親の中で、その型の要素が自分一人しか存在しない場合にのみ発火する。これは「単一アイテムの場合のレイアウト調整」という、UIの条件分岐をCSSだけで完結させる強力な武器だ。

/
アイテムが単一の時だけパディングを消す。
JSでDOMを監視してクラスを付け替える必要はない。
CSSだけで完結させることで、レンダリングの競合を防ぐ。
/
.list-item {
margin-bottom: 1rem;

&:only-of-type {
margin-bottom: 0;
padding: 2rem;
border: 1px solid #ccc;
}
}

このアプローチの最大の利点は、「非同期の競合を回避できる」ことにある。JSがDOMを操作した後にCSSクラスを付与するまでのわずかなラグ(FOUC: Flash of Unstyled Content)を、CSS側の論理で最初から無効化できる。これはUIの堅牢性を担保する上で極めて重要だ。

重大なバグ:型(Type)の混同を避ける

ここで、現場で最もよく見る「型」の罠を一つ共有しておく。

最初の段落

余計なノード

二番目の段落

/
p:nth-child(2) は span にマッチするため適用されない。
しかし、p:nth-of-type(2) は span を無視して二番目の p を特定する。
/
p:nth-of-type(2) {
color: red;
}

`:first-of-type` も同様だ。もし同じタグ名で別の役割を持つ要素が存在する場合(例えば、`div` で構成されたカードコンポーネントの中に、別の `div` で作られたアイコンが含まれている場合)、`:first-of-type` はそのアイコンにも反応してしまう。

解決策:
CSSのアーキテクチャとして「セレクタを汚染しない」ことが鉄則だ。常にクラス名(`.card-item` など)をセレクタの起点にし、型に依存しすぎない構成を心がけること。型擬似クラスは、あくまで「クラス指定の補助」として使うのが、大規模開発における正しい作法だ。

結論:CSSは「宣言」であり「論理」である

`:first-of-type` 系を使うということは、そのDOM構造が「特定の型で構成されている」という情報をブラウザに渡すことと同義だ。このメタデータを意識し、再計算コストを最小化し、JSへの依存を極限まで減らす。

現場で「なぜこのCSSが効かないのか」と悩んだ時、ブラウザの気持ちになって「今、この親要素の中には何番目のこの型のノードがあるのか?」と自問自答してほしい。その視点こそが、泥臭い実務を優雅なエンジニアリングへと変える鍵になるはずだ。

次は、これらを組み合わせて「CSSのみで実装する複雑な条件分岐」について話そうか。CSSは、まだまだ奥が深い。

コメント

タイトルとURLをコピーしました