Il s'agit de l'un des exemples les plus connus d'auteurs mal compris le :first-child
fonctionnement.Introduite en CSS2 , la :first-child
pseudo-classe représente le tout premier enfant de son parent . C'est ça. Il y a une idée fausse très répandue selon laquelle il ramasse l'élément enfant qui est le premier à correspondre aux conditions spécifiées par le reste du sélecteur composé. En raison du fonctionnement des sélecteurs (voir ici pour une explication), ce n'est tout simplement pas vrai.
Les sélecteurs de niveau 3 introduisent une :first-of-type
pseudo-classe , qui représente le premier élément parmi les frères et sœurs de son type d'élément. Cette réponse explique, avec des illustrations, la différence entre :first-child
et :first-of-type
. Cependant, comme avec :first-child
, il ne regarde aucune autre condition ou attribut. En HTML, le type d'élément est représenté par le nom de la balise. Dans la question, ce type est p
.
Malheureusement, il n'y a pas de :first-of-class
pseudo-classe similaire pour faire correspondre le premier élément enfant d'une classe donnée. Une solution de contournement Lea Verou et moi avons trouvée pour cela (bien que de manière totalement indépendante) consiste à appliquer d'abord les styles souhaités à tous vos éléments avec cette classe:
/*
* Select all .red children of .home, including the first one,
* and give them a border.
*/
.home > .red {
border: 1px solid red;
}
... puis "annuler" les styles des éléments avec la classe qui vient après le premier , en utilisant le combinateur frère général~
dans une règle prioritaire:
/*
* Select all but the first .red child of .home,
* and remove the border from the previous rule.
*/
.home > .red ~ .red {
border: none;
}
Maintenant, seul le premier élément avec class="red"
aura une bordure.
Voici une illustration de la façon dont les règles sont appliquées:
<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>
Aucune règle n'est appliquée; aucune bordure n'est rendue.
Cet élément n'a pas de classe red
, il est donc ignoré.
Seule la première règle est appliquée; une bordure rouge est rendue.
Cet élément a la classered
, mais il n'est précédé d'aucun élément avec la classe red
dans son parent. Ainsi la deuxième règle n'est pas appliquée, seulement la première, et l'élément garde sa frontière.
Les deux règles sont appliquées; aucune bordure n'est rendue.
Cet élément a la classe red
. Il est également précédé d'au moins un autre élément de la classe red
. Ainsi, les deux règles sont appliquées et la deuxième border
déclaration l'emporte sur la première, la "défaisant" pour ainsi dire.
En prime, bien qu'il ait été introduit dans Selectors 3, le combinateur général de frères et sœurs est en fait assez bien pris en charge par IE7 et plus récent, contrairement à :first-of-type
et :nth-of-type()
qui ne sont pris en charge que par IE9. Si vous avez besoin d'une bonne prise en charge du navigateur, vous avez de la chance.
En fait, le fait que le combinateur frère soit le seul composant important dans cette technique, et qu'il ait une telle prise en charge du navigateur, rend cette technique très polyvalente - vous pouvez l'adapter pour filtrer les éléments par d'autres choses, en plus des sélecteurs de classe:
Vous pouvez l'utiliser pour contourner :first-of-type
IE7 et IE8, en fournissant simplement un sélecteur de type au lieu d'un sélecteur de classe (encore une fois, plus d'informations sur son utilisation incorrecte ici dans une section ultérieure):
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 */
}
Vous pouvez filtrer par sélecteurs d'attributs ou tout autre sélecteur simple au lieu de classes.
Vous pouvez également combiner cette technique prioritaire avec des pseudo-éléments même si les pseudo-éléments ne sont techniquement pas de simples sélecteurs.
Notez que pour que cela fonctionne, vous devrez savoir à l'avance quels seront les styles par défaut pour vos autres éléments frères afin de pouvoir remplacer la première règle. De plus, comme cela implique de remplacer les règles en CSS, vous ne pouvez pas obtenir la même chose avec un seul sélecteur à utiliser avec l' API Selectors ou les localisateurs CSS de Selenium .
Il convient de mentionner que Selectors 4 introduit une extension de la :nth-child()
notation (à l'origine une pseudo-classe entièrement nouvelle appelée:nth-match()
), qui vous permettra d'utiliser quelque chose comme :nth-child(1 of .red)
au lieu d'une hypothétique.red:first-of-class
. Étant une proposition relativement récente, il n'y a pas encore suffisamment d'implémentations interopérables pour qu'elle soit utilisable sur les sites de production. Espérons que cela changera bientôt. En attendant, la solution de contournement que j'ai suggérée devrait fonctionner pour la plupart des cas.
Gardez à l'esprit que cette réponse suppose que la question recherche chaque premier élément enfant ayant une classe donnée. Il n'y a ni pseudo-classe ni même une solution CSS générique pour la nième correspondance d'un sélecteur complexe à travers le document entier - si une solution existe dépend fortement de la structure du document. jQuery fournit :eq()
, :first
, :last
et plus à cet effet, mais notez encore que ils fonctionnent très différemment :nth-child()
et al . En utilisant l'API Selectors, vous pouvez soit utiliser document.querySelector()
pour obtenir la toute première correspondance:
var first = document.querySelector('.home > .red');
Ou utilisez document.querySelectorAll()
avec un indexeur pour choisir une correspondance spécifique:
var redElements = document.querySelectorAll('.home > .red');
var first = redElements[0];
var second = redElements[1];
// etc
Bien que la .red:nth-of-type(1)
solution dans la réponse initialement acceptée par les travaux de Philip Daubmeier (qui a été Martyn mais supprimée depuis), elle ne se comporte pas comme vous vous y attendriez.
Par exemple, si vous souhaitez uniquement sélectionner le p
dans votre balisage d'origine:
<p class="red"></p>
<div class="red"></div>
... alors vous ne pouvez pas utiliser .red:first-of-type
(équivalent à .red:nth-of-type(1)
), car chaque élément est le premier (et le seul) de son type ( p
et div
respectivement), donc deux seront mis en correspondance par le sélecteur.
Lorsque le premier élément d'une certaine classe est également le premier de son type , la pseudo-classe fonctionnera, mais cela ne se produit que par coïncidence . Ce comportement est démontré dans la réponse de Philip. Au moment où vous collerez un élément du même type avant cet élément, le sélecteur échouera. Prendre le balisage mis à jour:
<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>
L'application d'une règle avec .red:first-of-type
fonctionnera, mais une fois que vous en aurez ajouté une autre p
sans la classe:
<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>
... le sélecteur échouera immédiatement, car le premier .red
élément est maintenant le deuxième p
élément.