【テクニカル・上級編】 擬似クラス :first-child と :last-child – CSS実践ガイド

こんにちは。フロントエンドの現場で日々、CSSの不可解な挙動やパフォーマンス低下と格闘しているエンジニアの皆さん。

今回は、CSSの基本中の基本である擬似クラス、`:first-child` と `:last-child` について、あえて「上級アーキテクチャの視点」から徹底的に解剖していこうと思う。
「最初と最後の要素を選ぶだけの単純なセレクタに、何言ってるんだ?」と思ったかもしれない。しかし、ブラウザのレンダリングエンジン(Blink, Gecko, WebKit)の内部挙動や、モダンなコンポーネント指向のアーキテクチャ、そして動的なDOM操作が絡み合う現代のWebアプリケーションにおいて、この2つの擬似クラスを適当に扱うことは、パフォーマンスの深刻なボトルネックや、予期せぬレイアウトシフト(CLS)を引き起こす引き金になり得るのだ。

今回は、机上の空論ではなく、戦場の最前線で培った知見を共有しよう。

—

1. 脳死で使っていませんか? `:first-child` の背後にあるDOMツリー走査の現実

まず大前提として、ブラウザがCSSセレクタを評価する方向を思い出してほしい。CSSセレクタは右から左(Key Selectorから祖先方向)へマッチングが行われる。

例えば、以下のようなセレクタを書いたとする。

.card-list .card-item:first-child {
/ スタイル定義 /
}

ブラウザはまずDOMツリーから `.card-item` をすべて探し出し、その親要素が `.card-list` であるかを右方向に検証し、さらにその要素が「親から見て本当に最初の兄弟要素(First Child)なのか」を判定する。

ここで問題になるのが、HTML構造の変動や、JavaScriptによる動的なDOMの差し込みだ。

「予期せぬHTMLの挿入」によるスタイルの崩壊

SPA(Single Page Application)やモダンなフレームワーク(React, Vue, Svelteなど)を使っていると、コンポーネントのライフサイクルの中で予期せぬラッパー要素や、フレームワーク特有のコメントノード、あるいはテキストノードが兄弟の間に挟まることがある。

ここで、`:first-child` の仕様における最大の罠を確認しておこう。
`:first-child` は「親要素における最初の子要素」を指す。したがって、以下のようなHTML構造があった場合、

アイテム1
アイテム2

もし `.container` の直下にコメントやテキストノードが入り込んだ場合、DOMノードの種類によっては `:first-child` の挙動が変わることはない(要素ノードのみがカウント対象のため)。しかし、フレームワークが生成する不隠な `