Este es uno de los ejemplos más conocidos de autores que no entienden cómo :first-child
funciona. Introducido en CSS2 , la :first-child
pseudoclase representa el primer hijo de su padre . Eso es. Existe una idea errónea muy común de que recoge cualquier elemento hijo que sea el primero en coincidir con las condiciones especificadas por el resto del selector compuesto. Debido a la forma en que funcionan los selectores (ver aquí una explicación), eso simplemente no es cierto.
El nivel 3 de selectores introduce una :first-of-type
pseudoclase , que representa el primer elemento entre hermanos de su tipo de elemento. Esta respuesta explica, con ilustraciones, la diferencia entre :first-child
y :first-of-type
. Sin embargo, al igual que con :first-child
, no considera otras condiciones o atributos. En HTML, el tipo de elemento está representado por el nombre de la etiqueta. En la pregunta, ese tipo es p
.
Desafortunadamente, no hay una :first-of-class
pseudoclase similar para hacer coincidir el primer elemento hijo de una clase dada. Una solución que Lea Verou y yo se nos ocurrió para esto (aunque de forma totalmente independiente) es aplicar primero los estilos deseados a todos sus elementos con esa clase:
/*
* Select all .red children of .home, including the first one,
* and give them a border.
*/
.home > .red {
border: 1px solid red;
}
... luego "deshacer" los estilos para los elementos con la clase que viene después de la primera , usando el combinador general de hermanos~
en una regla primordial:
/*
* Select all but the first .red child of .home,
* and remove the border from the previous rule.
*/
.home > .red ~ .red {
border: none;
}
Ahora solo el primer elemento con class="red"
tendrá un borde.
Aquí hay una ilustración de cómo se aplican las reglas:
<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>
No se aplican reglas; No se representa ningún borde.
Este elemento no tiene la clase red
, por lo que se omite.
Solo se aplica la primera regla; se representa un borde rojo.
Este elemento tiene la clase red
, pero no está precedido por ningún elemento con la clase red
en su elemento primario. Por lo tanto, la segunda regla no se aplica, solo la primera, y el elemento mantiene su borde.
Ambas reglas se aplican; No se representa ningún borde.
Este elemento tiene la clase red
. También está precedido por al menos otro elemento con la clase red
. Por lo tanto, se aplican ambas reglas, y la segunda border
declaración anula la primera, "deshaciéndola", por así decirlo.
Como beneficio adicional, aunque se introdujo en Selectores 3, el combinador de hermanos general está bastante bien soportado por IE7 y más reciente, a diferencia :first-of-type
y :nth-of-type()
solo soportado por IE9 en adelante. Si necesita un buen soporte para el navegador, tiene suerte.
De hecho, el hecho de que el combinador de hermanos es el único componente importante en esta técnica, y tiene un soporte de navegador tan sorprendente, hace que esta técnica sea muy versátil: puede adaptarlo para filtrar elementos por otras cosas, además de los selectores de clase:
Puede usar esto para evitar :first-of-type
en IE7 e IE8, simplemente proporcionando un selector de tipo en lugar de un selector de clase (nuevamente, más sobre su uso incorrecto aquí en una sección posterior):
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 */
}
Puedes filtrar por selectores de atributos o cualquier otro selector simple en lugar de clases.
También puede combinar esta técnica primordial con pseudoelementos aunque los pseudoelementos técnicamente no son simples selectores.
Tenga en cuenta que para que esto funcione, necesitará saber de antemano cuáles serán los estilos predeterminados para sus otros elementos hermanos para poder anular la primera regla. Además, dado que esto implica anular reglas en CSS, no puede lograr lo mismo con un solo selector para usar con la API de selectores o Selenium los localizadores CSS de .
Vale la pena mencionar que Selectores 4 introduce una extensión de la :nth-child()
notación (originalmente una pseudoclase completamente nueva llamada :nth-match()
), que le permitirá usar algo como :nth-child(1 of .red)
en lugar de una hipótesis.red:first-of-class
. Al ser una propuesta relativamente reciente, todavía no hay suficientes implementaciones interoperables para que pueda utilizarse en los sitios de producción. Esperemos que esto cambie pronto. Mientras tanto, la solución alternativa que he sugerido debería funcionar para la mayoría de los casos.
Tenga en cuenta que esta respuesta supone que la pregunta busca cada primer elemento secundario que tenga una clase determinada. No hay una pseudoclase ni una solución genérica de CSS para la enésima combinación de un selector complejo en todo el documento : si existe una solución depende en gran medida de la estructura del documento. jQuery proporciona :eq()
, :first
, :last
y más para este propósito, pero de nuevo nota que funcionan de manera muy diferente a partir de :nth-child()
et al . Usando la API de selectores, puede usar document.querySelector()
para obtener la primera coincidencia:
var first = document.querySelector('.home > .red');
O use document.querySelectorAll()
con un indexador para elegir una coincidencia específica:
var redElements = document.querySelectorAll('.home > .red');
var first = redElements[0];
var second = redElements[1];
// etc
Aunque la .red:nth-of-type(1)
solución en la respuesta original aceptada por Philip Daubmeier funciona (que fue originalmente escrita por Martyn pero eliminada desde entonces), no se comporta de la manera que esperarías.
Por ejemplo, si solo deseaba seleccionar el p
en su marcado original:
<p class="red"></p>
<div class="red"></div>
... entonces no puede usar .red:first-of-type
(equivalente a .red:nth-of-type(1)
), porque cada elemento es el primero (y único) de su tipo ( p
y div
respectivamente), por lo que ambos lo que el selector hará coincidir .
Cuando el primer elemento de una determinada clase es también el primero de su tipo , la pseudoclase funcionará, pero esto solo sucede por coincidencia . Este comportamiento se demuestra en la respuesta de Philip. En el momento en que pegue un elemento del mismo tipo antes de este elemento, el selector fallará. Tomando el marcado actualizado:
<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>
Aplicar una regla con .red:first-of-type
funcionará, pero una vez que agregue otra p
sin la clase:
<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>
... el selector fallará inmediatamente, porque el primer .red
elemento ahora es el segundo p
elemento.