CSS选择器:选取第一个类名为的元素

1306

我有许多具有类名为 red 的元素,但是我似乎无法使用以下CSS规则选择第一个具有 class =“red”的元素:

.home .red:first-child {
    border: 1px solid red;
}
<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的子元素?


请查看以下参考资料:链接链接 - Faegheh Mohammadian
25个回答

1865
这是作者误解 :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独立开发了一个解决方法(她先做的!),即首先将所需的样式应用于所有具有该类的元素:

/* 
 * Select all .red children of .home, including the first one,
 * and give them a border.
 */
.home > .red {
    border: 1px solid red;
}

然后,使用通用兄弟选择器 ~在一个覆盖规则中,对第一个元素之后的所有带有该类的元素“撤消”样式:

/* 
 * 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>

  1. 没有应用规则,也没有渲染边框。
    这个元素没有类名为red的class,因此被跳过。

  2. 只有第一条规则被应用,渲染红色边框。
    这个元素有类名为red的class,但它的父级中没有任何先前带有red class的元素。因此第二条规则不适用,只有第一条规则被应用,元素保留了其边框。

  3. 两条规则都被应用,没有渲染边框。
    这个元素有类名为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 {
         /* 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 */
     }
    
  • 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 APISelenium的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];
// etc

尽管Philip Daubmeier的原始接受答案中.red:nth-of-type(1)解决方案有效(最初由Martyn编写,但已被删除),但它并不像您期望的那样运行。

例如,如果您只想选择这里的p

<p class="red"></p>
<div class="red"></div>

如果每个元素都是其类型的第一个(且唯一一个)元素(例如 pdiv),则无法使用.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元素。


34
有没有办法模拟:last-of-class?选择一个类的最后一个元素。 - gen_Eric
1
@Gnuey:这是因为组合器是线性的。在评论中有点难以解释,但基本上我会说第一个> p意味着第二个p通过兄弟组合器是同一article的子元素,所以用article替换第二个p可以起到类似的作用,接下来的> p从那一点向下遍历了一个额外的层级。我还有几个其他的答案更详细地解释了它:https://dev59.com/o2865IYBdhLWcg3wYNVS#3851754 https://dev59.com/cWsz5IYBdhLWcg3wDzwA#8135729 - BoltClock
4
使用兄弟选择器的技巧不错。值得注意的是,这个技巧也适用于多个兄弟组合器,例如 ppp 将选择第三个及之后的项:https://jsfiddle.net/zpnnvedm/1/。 - Legolas
使用 ~ 运算符的确是个很巧妙的技巧,但为什么不考虑使用 + 运算符作为更简单的解决方案呢?span + p.red - elrobe
1
@ElRoBe:这需要假设第一个p.red总是直接跟在span后面。有时,您可能会发现自己无法对标记做出假设,因为它可能以某些方式变化。例如,那个span可能不总是存在,第一个p.red可能不直接跟随它,或者在同一父级中可能有多个span + p.red对,而您只想影响第一个。但是,如果您能够对标记做出这些假设或保证,那么这些更简单的解决方案将可供您使用。 - BoltClock
显示剩余2条评论

398

:first-child选择器旨在像其名称所示的那样选择父标记的第一个子标记。因此,此示例将起作用(只需在这里尝试:here):

<body>
    <p class="red">first</p>
    <div class="red">second</div>
</body>

如果你把标签嵌套在不同的父标签中,或者如果你的class为red的标签不是父标签下的第一个标签,那么这种方法就行不通了。

还要注意的是,这不仅适用于整个文档中的第一个此类标签,而且每次包裹它的新父元素都是如此,例如:

<div>
    <p class="red">first</p>
    <div class="red">second</div>
</div>
<div>
    <p class="red">third</p>
    <div class="red">fourth</div>
</div>

firstthird将会变成红色。

对于您的情况,您可以使用:nth-of-type选择器:

.red:nth-of-type(1)
{
    border:5px solid red;
} 
<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>

感谢Martyn的贡献,他删除了包含这种方法的答案。

有关:nth-child():nth-of-type()的更多信息,请访问http://www.quirksmode.org/css/nthchild.html

请注意,这是一个CSS3选择器,因此某些已过时的浏览器版本可能无法按预期运行(例如IE8或更早版本)。有关更多详细信息,请访问https://caniuse.com/?search=nth-of-type


33
阅读这个内容时我有些困惑。严格来说,.red:nth-of-type(1)会选择任何符合以下两个条件的元素:(a)是其元素类型的第一个子元素,(b)具有“red”类。因此,在示例中,如果第一个<p>没有“red”类,则不会被选中。或者,如果<span>和第一个<p>都有“red”类,则它们都会被选中。http://jsfiddle.net/fvAxn/ - David
2
几个月前@David说得对,:nth-of-type()不是一个好的解决方案。我提供了一个替代答案,应该更可靠,并且作为奖励,在IE7+中也可以使用,而:nth-of-type()则不能。 - BoltClock
9
@Dan Mundy:“:first-of-type”相当于“:nth-of-type(1)”,因此它也能正常工作。但出于与我回答中相同的原因,它也可能失败。请注意不要改变原意。 - BoltClock
6
@David 我相信 :nth-of-type 中的“type”指的是“元素/标签”。因此它只考虑元素,而不考虑类之类的东西。 - gcampbell
5
是的,这就是它的意思,这也是为什么这个答案有缺陷的原因。选择使用“type”这个词是为了不将选择器与HTML/XML耦合在一起,因为并非所有语言都有定义元素的“标签”概念。 - BoltClock
显示剩余4条评论

99

正确答案是:

.red:first-child, :not(.red) + .red { border:5px solid red }

第一部分:如果元素是其父元素中的第一个,并且具有"class = red",则它应该获得边框。
第二部分:如果".red"元素不是其父级中的第一个元素,但紧随一个没有".red"类的元素,则它也应该获得此边框。

JSFiddle代码示例

Philip Daubmeier的答案虽然被接受,但是不正确-请参见附带的JSFiddle代码示例。
BoltClock的答案可行,但不必要地定义和覆盖样式
(特别是在其本来会继承不同边框的情况下声明border:none是一个问题)

编辑: 如果您多次使用非红色后面跟随“红色”,则每个“第一个”红色都将获得该边框。为了防止这种情况发生,需要使用BoltClock的答案。请参见此JSFiddle代码示例


3
这个答案并没有错误,对于给定的标记,通配选择器正确的,但我需要重申一下,编辑中提到的情况正是我强调当你无法保证标记结构时至少需要覆盖的原因——仅仅因为.red跟在:not(.red)后面,并不总是使它成为其兄弟姐妹中的第一个.red。如果需要继承边框样式,只需要在覆盖规则中声明border: inherit而不是border: none即可。 - BoltClock

38

我很惊讶没有人提到最干净的解决方案:

.red:not(.red ~ .red) {
    border: 1px solid red;
}
<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>


3
一个不是“以一个.red为前导的.red”的.red。在这个例子中,只有第一个是这样的。但是如果在它们之间插入另一个类,则无法使用该方法。注意:.red:not(.red ~ *)也可以工作(“一个不是“任何东西”后面跟着一个.red.red”)。 - hl037_
2
@hl037_ 为什么它不工作?你确定你没有混淆 '+' 和 '~' 选择器,即时兄弟和任意距离兄弟吗? - Arioch 'The
如何选择具有“red”类的最后一个元素? - user1412586
2
需要一些解释。仅提供代码的答案是不鼓励的。 - ihor.eth
很棒的答案。这里是解释:元素1~元素2选择器匹配在元素1之前出现的元素2。 两个元素必须有相同的父级,但元素2不一定要紧接着元素1出现。 - 1haker
它仍然针对我的WordPress Elementor页面上具有给定类的所有元素进行定位(这可能承认有一些相当混乱的CSS,但仍然是个人经验证明它并不是万无一失的)。 - velkoon

31

以上回答过于复杂。

.class:first-of-type { }

这将选择第一个类型的类。MDN来源

注意:已在Chrome 91和Firefox 89上进行了测试,测试时间为2021年6月。


2
first-type has been renamed to first-of-type - Gabriel Fair
48
它不能用于类。这就是它应该的工作方式,因为选择类的第n个元素比选择标签的第n个元素更有用。但':first-of-type'只能按tagName工作。如果碰巧'first-of-class'和'first-of-tag'指的是同一个元素(通常是这样),那么你很容易会以为它可以按照类来工作;然后在遇到它们不一样的情况时,就会想知道哪里出了问题。 - blerg
2
@Bernesto,当您遇到多个相同 "类型" 的元素时,比如 <span> 并且其中几个具有类 highlight.highlight:first-of-type 选择器将选择带有选定类的 "类型" 的第一个实例,在这个例子中是第一个 <span>,而不是选定类 highlight 的第一个实例。只有当该 span 的第一个实例也具有 highlight 类时,样式才会被实现。(https://codepen.io/andrewRmillar/pen/poJKJdJ) - AndrewRMillar
2
我的答案如此复杂的原因是因为它的最后一部分以及我对其他几个答案的评论精确地解释了为什么这是有缺陷的。在事实两年之后将MDN的链接添加到您的答案中并没有任何支持作用,因为您没有尝试将MDN的材料与其相关联 - 可能是因为您无法做到。 - BoltClock

25
你可以使用first-of-typenth-of-type(1)nth-child(1 of .red)

.red {
  color: green;  
}

/* .red:nth-of-type(1) */
/* .red:first-of-type */
.home :nth-child(1 of .red) {
  color: red;  
}
<div class="home">
  <span>blah</span>
  <p>not red</p>
  <p class="red">first</p>
  <p class="red">second</p>
  <p class="red">third</p>
  <p class="red">fourth</p>
</div>


11
如果在span和第一个具有red类的p元素之间有一个<p></p>,那么它将无法正常工作。请看这个JSFiddle - Francisco Romero
@FranciscoRomero 使用 :nth-child(1 of .red) 将修复这个问题 - undefined
在我写评论的时候,那个选择器还不存在。我甚至不知道那个属性,但我喜欢它,它真的很有必要。谢谢你分享它。不过,它目前还没有得到广泛的支持(https://caniuse.com/?search=%3Anth-child(1%20of%20.foo))。 - undefined
nth-child(1 of .red) 在2023年春季得到了广泛的支持,详见caniuse - undefined

11
为了匹配你的选择器,该元素必须具有类名为red,并且必须是其父级的第一个子元素。
<div>
    <span class="red"></span> <!-- MATCH -->
</div>

<div>
    <span>Blah</span>
    <p class="red"></p> <!-- NO MATCH -->
</div>

<div>
    <span>Blah</span>
    <div><p class="red"></p></div> <!-- MATCH -->
</div>

10

由于其他答案已经涵盖了其中的错误,我将尝试另一半——如何修复它。不幸的是,我不知道是否有CSS only的解决方案,至少我能想到的没有。但有一些其他选项...

  1. 在生成元素时为其分配一个first类,像这样:

<p class="red first"></p>
<div class="red"></div>

CSS:

.first.red {
  border:5px solid red;
}

这个 CSS 只匹配同时具有 firstred 类的元素。

  • 或者,使用 JavaScript 进行相同的操作,例如这里是使用 jQuery 执行此操作的代码,使用与上面相同的 CSS:

    $(".red:first").addClass("first");
    

  • 我曾经想出了一种仅使用CSS的解决方案[a while ago](https://dev59.com/7nA65IYBdhLWcg3wzyL8#3615559); 我在这里复制它作为一个标准答案。 - BoltClock
    1
    只要没有像 :first-of-class 这样的东西,我建议为第一个元素添加一个额外的类。看起来现在是最简单的解决方案。 - Avatar

    9
    我在项目中遇到了这个问题。

    div > .b ~ .b:not(:first-child) {
     background: none;
    }
    div > .b {
        background: red;
    }
    <div>
          <p class="a">The first paragraph.</p>
          <p class="a">The second paragraph.</p>
          <p class="b">The third paragraph.</p>
          <p class="b">The fourth paragraph.</p>
      </div>


    应用我回答中的技巧时,不需要使用:not(:first-child)。 ~组合器意味着:not(:first-child)将始终匹配,因此是多余的-它对您的选择器唯一的影响是额外的特异性。而且,您不需要这种额外的特异性,因此可以(并且可能应该)省略它。 - BoltClock

    5
    我使用以下CSS为列表ul li设置背景图像:

    #footer .module:nth-of-type(1)>.menu>li:nth-of-type(1){
      background-position: center;
      background-image: url(http://monagentvoyagessuperprix.j3.voyagesendirect.com/images/stories/images_monagentvoyagessuperprix/layout/icon-home.png);
      background-repeat: no-repeat;
    }
    <footer id="footer">
      <div class="module">
        <ul class="menu ">
          <li class="level1 item308 active current"></li>
          <li> </li>
        </ul> 
      </div>
      <div class="module">
        <ul class="menu "><li></li>
          <li></li> 
        </ul>
      </div>
      <div class="module">
        <ul class="menu ">
          <li></li>
          <li></li>
        </ul>
      </div>
    </footer>


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接