这是作者误解
:first-child
工作方式的最著名的例子之一。
CSS2引入,
:first-child
伪类表示
其父级中的第一个子元素。就是这样。有一个非常普遍的误解,即它会选择与其余复合选择器指定条件相匹配的第一个子元素。由于选择器的工作方式(请参见
此处以获取说明),这完全不正确。
Selectors level 3引入了 :first-of-type
伪类, 它代表其元素类型的兄弟姐妹中的第一个元素。
这个答案 通过插图解释了
:first-child
和
:first-of-type
之间的区别。但是,与
:first-child
一样,它不考虑任何其他条件或属性。在 HTML 中,元素类型由标签名称表示。在问题中,该类型为
p
。
很遗憾,没有类似于:first-of-class
的伪类可以匹配给定类的第一个子元素。在本回答发布时,Selectors Level 4最新发布了FPWD引入了一个:nth-match()
伪类,它是围绕着现有选择器机制设计的,正如我在第一段中提到的那样,通过添加选择器列表参数,您可以提供其余复合选择器以获得所需的过滤行为。近年来,这个功能已被并入到:nth-child()
本身中,选择器列表出现为可选的第二个参数,以简化事情,同时避免误认为:nth-match()
匹配整个文档(请参见下面的最后一条注释)。
虽然我们正在等待跨浏览器支持(说真的,已经快10年了,过去5年只有单一实现),但我和Lea Verou独立开发了一个解决方法(她先做的!),即首先将所需的样式应用于所有具有该类的元素:
.home > .red {
border: 1px solid red;
}
然后,使用通用兄弟选择器 ~
在一个覆盖规则中,对第一个元素之后的所有带有该类的元素“撤消”样式:
.home > .red ~ .red {
border: none;
}
现在只有带有
class="red"
的第一个元素才会有边框。
下面是规则应用的示例:
.home > .red {
border: 1px solid red;
}
.home > .red ~ .red {
border: none;
}
<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
的class,因此被跳过。
只有第一条规则被应用,渲染红色边框。
这个元素有类名为red
的class,但它的父级中没有任何先前带有red
class的元素。因此第二条规则不适用,只有第一条规则被应用,元素保留了其边框。
两条规则都被应用,没有渲染边框。
这个元素有类名为red
的class。它之前至少有一个其他带有red
class的元素。因此两条规则都被应用,第二个border
声明覆盖了第一个,从而“撤消”了它。
作为奖励,虽然它是在选择器3中引入的,但通用兄弟组合器实际上在IE7及更高版本中得到了很好的支持,不像:first-of-type
和:nth-of-type()
只有在IE9及更高版本中才支持。如果您需要良好的浏览器支持,那么您很幸运。
实际上,兄弟组合器是这种技术中唯一重要的组成部分,并且它有如此惊人的浏览器支持,使得这种技术非常灵活 - 您可以根据其他内容适应它来过滤元素,除了类选择器之外:
You can use this to work around :first-of-type
in IE7 and IE8, by simply supplying a type selector instead of a class selector (again, more on its incorrect usage in the question in a later section):
article > p {
}
article > p ~ p {
}
You can filter by attribute selectors or any other simple selectors instead of classes.
You can also combine this overriding technique with pseudo-elements even though pseudo-elements technically aren't simple selectors.
请注意,为了使此方法有效,您需要预先知道其他兄弟元素的默认样式,以便覆盖第一个规则。另外,由于这涉及到在CSS中覆盖规则,您无法使用单个选择器实现与
Selectors API或
Selenium的CSS定位符相同的效果。
最后,请记住,本答案假定问题是寻找具有给定类的
任意数量的第一个子元素。对于整个文档中复杂选择器的第n次匹配,既没有伪类也没有通用的CSS解决方案-是否存在解决方案取决于文档结构。jQuery提供了
:eq()
、
:first
、
:last
等功能来实现此目的,但请再次注意
它们与:nth-child()
等功能非常不同。使用选择器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];
尽管
Philip Daubmeier的原始接受答案中
.red:nth-of-type(1)
解决方案有效(最初由
Martyn编写,但已被删除),但它并不像您期望的那样运行。
例如,如果您只想选择这里的p
:
<p class="red"></p>
<div class="red"></div>
如果每个元素都是其类型的第一个(且唯一一个)元素(例如 p
和 div
),则无法使用.red:first-of-type
(等同于.red:nth-of-type(1)
),因为选择器将匹配两者。
当某个类的第一个元素同时也是其类型的第一个元素时,伪类将起作用,但这只是偶然发生的。这种行为在Philip的答案中有所体现。一旦在此元素之前插入相同类型的元素,选择器就会失败。以问题中的标记为例:
<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
元素。