这是作者误解:first-child 工作原理的最著名例子之一。 Introduced in CSS2,:first-child 伪类代表其父级的第一个子级。而已。有一个非常普遍的误解,即它会选择第一个匹配复合选择器其余部分指定的条件的子元素。由于选择器的工作方式(请参阅here 以获得解释),这根本不是真的。
Selectors level 3 introduces a :first-of-type pseudo-class,表示其元素类型的兄弟元素中的第一个元素。 This answer 用插图解释了:first-child 和:first-of-type 之间的区别。但是,与:first-child 一样,它不查看任何其他条件或属性。在 HTML 中,元素类型由标签名称表示。在问题中,该类型是p。
不幸的是,没有类似的:first-of-class 伪类来匹配给定类的第一个子元素。在此答案首次发布时,the newly published FPWD of Selectors level 4 introduced an :nth-match() pseudo-class,围绕现有的选择器机制设计,正如我在第一段中提到的那样,通过添加一个选择器列表参数,您可以提供复合选择器的其余部分以获得所需的过滤行为.近年来,此功能为subsumed into :nth-child() itself,选择器列表作为可选的第二个参数出现,以简化事情并避免:nth-match() 在整个文档中匹配的错误印象(请参阅下面的最后注释)。
虽然我们在等待 cross-browser support(说真的,已经快 10 年了,而在过去的 5 年中只有一个实现),但 Lea Verou 和我独立开发的一个解决方法(她先做了) !) 是首先将您想要的样式应用于该类的所有元素:
/*
* Select all .red children of .home, including the first one,
* and give them a border.
*/
.home > .red {
border: 1px solid red;
}
...然后“撤消”具有类的元素的样式在第一个之后,在覆盖规则中使用the general sibling combinator ~:
/*
* Select all but the first .red child of .home,
* and remove the border from the previous rule.
*/
.home > .red ~ .red {
border: none;
}
现在只有class="red" 的第一个元素会有边框。
以下是如何应用规则的说明:
.home > .red {
border: 1px solid red;
}
.home > .red ~ .red {
border: none;
}
<div class="home">
<span>blah</span> <!-- [1] -->
<p class="red">first</p> <!-- [2] -->
<p class="red">second</p> <!-- [3] -->
<p class="red">third</p> <!-- [3] -->
<p class="red">fourth</p> <!-- [3] -->
</div>
-
不应用任何规则;不呈现边框。
该元素没有red 类,因此被跳过。
-
只应用第一条规则;呈现红色边框。
该元素具有类red,但它的父元素中没有任何具有red 类的元素。因此不应用第二条规则,只应用第一条规则,并且元素保持其边界。
-
两个规则都适用;不呈现边框。
该元素具有类red。它前面还有至少一个其他元素,其类为red。因此,两个规则都适用,第二个 border 声明覆盖第一个,从而“撤消”它,可以这么说。
作为奖励,虽然它是在 Selectors 3 中引入的,但通用兄弟组合器实际上在 IE7 和更新版本中得到了很好的支持,不像 :first-of-type 和 :nth-of-type() 仅在 IE9 及更高版本中支持。如果您需要良好的浏览器支持,那么您很幸运。
事实上,兄弟组合器是该技术中唯一重要的组件,并且它具有如此惊人的浏览器支持,这使得该技术非常通用——您可以通过以下方式将其用于过滤元素除了类选择器之外的其他东西:
-
您可以使用它来解决 IE7 和 IE8 中的 :first-of-type 问题,只需提供类型选择器而不是类选择器(同样,稍后部分的问题中会详细介绍其错误用法):
article > p {
/* Apply styles to article > p:first-of-type, which may or may not be :first-child */
}
article > p ~ p {
/* Undo the above styles for every subsequent article > p */
}
-
您可以通过attribute selectors 或任何其他简单的选择器而不是类进行过滤。
-
您也可以将此覆盖技术与pseudo-elements 结合使用,即使伪元素在技术上并不是简单的选择器。
请注意,为了使其正常工作,您需要提前知道其他同级元素的默认样式是什么,以便您可以覆盖第一条规则。此外,由于这涉及覆盖 CSS 中的规则,因此您无法使用与 Selectors API 或 Selenium 的 CSS 定位器一起使用的单个选择器来实现相同的目的。
最后一点,请记住,此答案假定问题是在寻找具有给定类的 任意数量 个第一个子元素。对于复杂选择器的第 n 次匹配整个文档,既没有伪类,也没有通用 CSS 解决方案——解决方案是否存在很大程度上取决于文档结构。 jQuery 为此提供了:eq()、:first、:last 等,但请再次注意they function very differently from :nth-child() et al。使用 Selectors API,您可以使用 document.querySelector() 来获取第一个匹配项:
var first = document.querySelector('.home > .red');
或者使用document.querySelectorAll() 和索引器来选择任何特定的匹配:
var redElements = document.querySelectorAll('.home > .red');
var first = redElements[0];
var second = redElements[1];
// etc
尽管Philip Daubmeier 最初接受的答案中的.red:nth-of-type(1) 解决方案有效(最初由Martyn 编写,但此后被删除),但它的行为方式与您的预期不同。
例如,如果您只想在此处选择p:
<p class="red"></p>
<div class="red"></div>
...那么你就不能使用.red:first-of-type(等价于.red:nth-of-type(1)),因为每个元素都是其类型中的第一个(也是唯一一个)(分别为p和div),所以两者都会被选择器匹配。
当某个类的第一个元素也是其类型的第一个时,伪类会起作用,但这只是巧合。这种行为在菲利普的回答中得到了证明。当您在该元素之前插入相同类型的元素时,选择器将失败。从问题中提取标记:
<div class="home">
<span>blah</span>
<p class="red">first</p>
<p class="red">second</p>
<p class="red">third</p>
<p class="red">fourth</p>
</div>
使用.red:first-of-type 应用规则将起作用,但是一旦您添加另一个p 而不使用该类:
<div class="home">
<span>blah</span>
<p>dummy</p>
<p class="red">first</p>
<p class="red">second</p>
<p class="red">third</p>
<p class="red">fourth</p>
</div>
...选择器将立即失败,因为第一个 .red 元素现在是 第二个 p 元素。