Questo è uno degli esempi più noti di autori che fraintendono il modo in cui :first-child
funziona. Introdotta in CSS2 , la :first-child
pseudo-classe rappresenta il primogenito del suo genitore . Questo è tutto. C'è un malinteso molto comune sul fatto che raccoglie qualunque elemento figlio sia il primo a soddisfare le condizioni specificate dal resto del selettore composto. A causa del modo in cui funzionano i selettori (vedere qui per una spiegazione), semplicemente non è vero.
Il livello 3 dei selettori introduce una :first-of-type
pseudo-classe , che rappresenta il primo elemento tra i fratelli del suo tipo di elemento. Questa risposta spiega, con illustrazioni, la differenza tra :first-child
e :first-of-type
. Tuttavia, come nel caso :first-child
, non considera altre condizioni o attributi. In HTML, il tipo di elemento è rappresentato dal nome del tag. Nella domanda, quel tipo è p
.
Sfortunatamente, non esiste una :first-of-class
pseudo-classe simile per abbinare il primo elemento figlio di una data classe. Una soluzione alternativa che Lea Verou ed io abbiamo escogitato per questo (anche se in modo totalmente indipendente) è applicare prima gli stili desiderati a tutti i tuoi elementi con quella classe:
/*
* Select all .red children of .home, including the first one,
* and give them a border.
*/
.home > .red {
border: 1px solid red;
}
... quindi "annulla" gli stili per gli elementi con la classe che segue la prima , usando il combinatore di fratelli generali~
in una regola prioritaria:
/*
* Select all but the first .red child of .home,
* and remove the border from the previous rule.
*/
.home > .red ~ .red {
border: none;
}
Ora solo il primo elemento con class="red"
avrà un bordo.
Ecco un'illustrazione di come vengono applicate le regole:
<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>
Non vengono applicate regole; nessun bordo viene reso.
Questo elemento non ha la classe red
, quindi viene saltato.
Viene applicata solo la prima regola; viene visualizzato un bordo rosso.
Questo elemento ha la classe red
, ma non è preceduto da alcun elemento con la classe red
nel suo genitore. Pertanto la seconda regola non viene applicata, solo la prima e l'elemento mantiene il suo bordo.
Vengono applicate entrambe le regole; nessun bordo viene reso.
Questo elemento ha la classe red
. È inoltre preceduto da almeno un altro elemento con la classe red
. In tal modo vengono applicate entrambe le regole e la seconda border
dichiarazione sovrascrive la prima, quindi "annullandola", per così dire.
Come bonus, sebbene sia stato introdotto in Selettori 3, il combinatore di fratelli generali è in realtà abbastanza ben supportato da IE7 e più recenti, a differenza :first-of-type
e :nth-of-type()
che sono supportati solo da IE9 in poi. Se hai bisogno di un buon supporto per il browser, sei fortunato.
In effetti, il fatto che il combinatore di pari livello sia l'unico componente importante di questa tecnica e abbia un supporto del browser così straordinario, rende questa tecnica molto versatile: puoi adattarla per filtrare gli elementi con altre cose, oltre ai selettori di classe:
Puoi usarlo per aggirare :first-of-type
IE7 e IE8, semplicemente fornendo un selettore di tipo anziché un selettore di classe (di nuovo, più sul suo uso errato qui in una sezione successiva):
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 */
}
È possibile filtrare per selettori di attributo o qualsiasi altro selettore semplice anziché per classe.
Puoi anche combinare questa tecnica prioritaria con pseudo-elementi anche se tecnicamente gli pseudo-elementi non sono semplici selettori.
Nota che per far funzionare tutto ciò, dovrai sapere in anticipo quali saranno gli stili predefiniti per gli altri tuoi fratelli e sorelle in modo da poter sovrascrivere la prima regola. Inoltre, poiché ciò implica l'override delle regole nei CSS, non è possibile ottenere la stessa cosa con un singolo selettore da utilizzare con l' API Selector o Selenium i localizzatori CSS di .
Vale la pena ricordare che Selectors 4 introduce un'estensione alla :nth-child()
notazione (originariamente una pseudo-classe completamente nuova chiamata :nth-match()
), che ti permetterà di usare qualcosa di simile :nth-child(1 of .red)
al posto di un ipotetico .red:first-of-class
. Essendo una proposta relativamente recente, non ci sono abbastanza implementazioni interoperabili per essere utilizzabili nei siti di produzione. Spero che questo cambierà presto. Nel frattempo, la soluzione che ho suggerito dovrebbe funzionare nella maggior parte dei casi.
Tieni presente che questa risposta presuppone che la domanda cerchi ogni primo elemento figlio che ha una determinata classe. Non esiste una pseudo-classe né una soluzione CSS generica per l'ennesima corrispondenza di un selettore complesso in tutto il documento - se esiste una soluzione dipende fortemente dalla struttura del documento. jQuery fornisce :eq()
, :first
, :last
e più per questo scopo, ma nota ancora che essi funzionano in modo molto diverso da :nth-child()
et al . Utilizzando l'API Selettori, è possibile utilizzare document.querySelector()
per ottenere la prima corrispondenza:
var first = document.querySelector('.home > .red');
Oppure usa document.querySelectorAll()
con un indicizzatore per scegliere una corrispondenza specifica:
var redElements = document.querySelectorAll('.home > .red');
var first = redElements[0];
var second = redElements[1];
// etc
Sebbene la .red:nth-of-type(1)
soluzione nella risposta originale accettata da Philip Daubmeier funzioni (che è stata originariamente scritta da Martyn ma cancellata da allora), non si comporta come ti aspetteresti.
Ad esempio, se si desidera selezionare solo il p
markup originale:
<p class="red"></p>
<div class="red"></div>
... quindi non puoi usare .red:first-of-type
(equivalente a .red:nth-of-type(1)
), perché ogni elemento è il primo (e unico) uno del suo tipo ( p
e div
rispettivamente), quindi entrambi saranno abbinati dal selettore.
Quando il primo elemento di una determinata classe è anche il primo del suo tipo , la pseudo-classe funzionerà, ma ciò accade solo per coincidenza . Questo comportamento è dimostrato nella risposta di Filippo. Nel momento in cui ti attacchi a un elemento dello stesso tipo prima di questo elemento, il selettore fallirà. Prendendo il markup aggiornato:
<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'applicazione di una regola con .red:first-of-type
funzionerà, ma una volta aggiunta un'altra p
senza 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>
... il selettore fallirà immediatamente, perché il primo .red
elemento è ora il secondo p
elemento.