:first-of-type选不到“第一个”元素是因为它只匹配同类型元素中首个出现的,而非父容器中位置第一的元素;:first-child则只认子节点位置序号。

为什么 :first-of-type 有时选不到你认为的“第一个”元素
因为 :first-of-type 只关心「同类型元素中的第一个」,不看它在父容器里实际排第几。比如父元素里先有一个 <p>,再一个 <div>,又一个 <p>,那么第二个 <p> 不是 :first-of-type——第一个 <p> 才是,哪怕它前面还有别的标签。
而 :first-child 完全不看标签名,只认位置:只要它是父元素的第一个子节点,就匹配。
-
:first-child等价于「位置为 1 的子元素」 -
:first-of-type等价于「该标签类型在兄弟中首次出现的那个」 - 如果第一个子元素恰好也是该类型的首个出现,两个伪类效果一致;否则结果不同
用 <section> 套 <h2> 和 <p> 时怎么精准加顶部间距
常见需求:给每个 <section> 里第一个 <h2> 上边留空,但不想影响其他 <h2>。这时候用 :first-child 很危险——万一 <section> 开头是注释、空格文本节点,或前置了 <div class="meta">,:first-child 就失效了。
更稳妥的是:section h2:first-of-type。它只找 <section> 内部所有 <h2> 中排最前的那个,不管前面有没有别的元素。
立即学习“前端免费学习笔记(深入)”;
section h2:first-of-type { margin-top: 2rem; }
- 即使
<section>以<svg>或<!-- comment -->开头,:first-of-type仍能命中首个<h2> -
:first-child在这类 HTML 中大概率不生效,且难以调试 - 注意:IE8 不支持
:first-of-type,如需兼容得用 js 补位
:first-of-type 对空格、换行、注释节点完全免疫
HTML 解析时,换行和缩进会生成文本节点(哪怕只含空白),注释也是独立节点。这些都会干扰 :first-child 判断——它要求目标元素必须是子节点列表索引为 0 的那个。
而 :first-of-type 是在所有兄弟元素中按标签名分组后筛选,跳过非元素节点(文本、注释)直接比对标签类型。
- 下面这段 HTML 中,
h2:first-child不会匹配任何<h2>:<section> <!-- intro --> <h2>Title</h2> <p>Text</p> </section> - 但
h2:first-of-type依然有效,因为它是该类型在兄弟中的第一次出现 - 这个差异在使用模板引擎(如 Handlebars、Vue SFC)时尤其明显,它们常注入注释或空文本节点
嵌套结构中误用 :first-of-type 的典型陷阱
容易想当然地写 article > *:first-of-type 以为能选中 article 下第一个任意标签,但实际上它会分别匹配每个类型里的第一个——比如同时给首个 <h1>、首个 <p>、首个 <img> 都加样式。
真要选「子元素中第一个非空元素」,css 没有原生方案,得靠其他方式:
- 用
article > :first-child+ 后续选择器排除已知干扰节点(如article > :not(comment):not([hidden]):first-child不生效,因为伪类不能过滤注释) - 更可靠的是加 class(如
article > .lead)或用 JS 标记article.children[0] -
:first-of-type必须带具体标签名才有意义,单独配*会失去类型判断依据
真正需要「第一个子元素」语义时,别绕弯子——要么确保 HTML 结构干净,要么接受用 JS 控制更稳当。