:tiene() | Trucos CSS - Trucos CSS

CSS :has() pseudoclass selecciona elementos que contienen otros elementos que coinciden con el selector pasado en sus argumentos. A menudo se le llama "selector principal" debido a su capacidad para seleccionar un elemento principal en función de los elementos secundarios que contiene y aplicar estilos al principal.

/* Select the .card element when it 
   contains a <figure> immediately
   followed by a paragraph. */
.card:has(figure + p) {
  flex-direction: row;
}

Este ejemplo selecciona un elemento con .card clase cuando contiene un <figure> elemento que es seguido inmediatamente por un <p> elemento:

<article class="card">
  <figure></figure>
  <p></p>
</article>

Esto es extremadamente útil para escribir estilos para componentes que pueden o no contener ciertos elementos dentro, como una cuadrícula de mapa, donde un elemento de mapa siempre tiene un párrafo pero puede no tener una imagen que lo acompañe.

¿Imagen o no imagen? Esta es la pregunta.

De esa forma, en situaciones en las que no siempre sabe lo que implica el marcado, puede escribir estilos que coincidan con esas condiciones.

:has() se determina en Especificación de selectores de nivel 4 donde se describe como la "pseudoclase relacional" debido a su capacidad para asignar selectores en función de la relación de un elemento con otros elementos.

Uso principal

El siguiente HTML contiene dos <button> Uno de ellos tiene un icono SVG.

<!-- Plain button -->
<button>Add</button>

<!-- Button with SVG icon -->
<button>
  <svg></svg>
  Add
</button>

Ahora, supongamos que desea aplicar estilos solo al <button> quién tiene <svg> elemento en su interior.

:has() es perfectamente adecuado para el trabajo:

button:has(svg) {
  /* Styles */
}

Él :has() selector nos permite distinguir entre un botón que hay descendiente <svg>y uno que no.

Sintaxis

:has( <unforgiving-relative-selector-list> )

A una lista despiadada para elegir se refiere a los argumentos que se pasan en :has() argumentos al selector, que es una lista de elementos separados por comas que se evalúan juntos en función de su relación con el elemento principal.

article:has(ol, ul) {
  /* Matches an <article> that contains either
     an ordered or unordered list. */
}

Desglosaremos la naturaleza "imperdonable" de la lista de argumentos con más detalle en un momento.

especificidad

Uno de los aspectos más interesantes de :has() es que su especificidad está determinada por el elemento más específico en su lista de argumentos。 Digamos que tenemos las siguientes reglas de estilo:

article:has(.some-class, #id, img) {
  background: #000;
}

article.some-class {
  background: #fff;
}

Tenemos dos reglas, las cuales eligen <article> elemento para cambiar su fondo. ¿Qué fondo obtiene este HTML?

<article>
  <div class="some-class"></div>
</article>

Podrías pensar que se está poniendo blanco (#fff) de fondo porque viene más tarde en la cascada. Pero dado que la lista de argumentos para :has() incluye otros selectores que debemos considerar más específico uno en la lista para determinar la verdadera especificidad de esta primera regla #id después.

Vamos a compararlos:

  • article.some-class generar un resultado de (0,1,1)
  • article:has(.some-class, #id, img) generar un resultado de (1,0,1)

¡La primera regla gana! El artículo se vuelve negro (#000) antecedentes.

¡Examen sorpresa!

¿Qué color crees que gana en el siguiente ejemplo?

article:has(h1, .title) a { 
  color: red; 
}

article h1 a {
  color: green;
}

¡Muéstrame la respuesta!

/* Specificity: (0,1,2) */
article:has(h1, .title) a {
  color: red; /* 🏆 Winner! */
}

/* Specificity: (0,0,3) */
article h1 a {
  color: green;
}

:has() es un selector "implacable"

Se ha presentado el primer borrador de las especificaciones. :has() me gusta "selector indulgente":

:has( <forgiving-relative-selector-list> )

La idea es que la lista pueda contener un selector inválido e ignorarlo.

/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }

::-scoobydoo es un pseudo-elemento inválido completamente ficticio que no existe. Si :has() fue "perdonador", este selector falso simplemente se ignorará mientras que el resto de los argumentos se analizan normalmente.

Pero más tarde, debido a un conflicto con el comportamiento de jQuerydecidió definir los autores de la especificación :has() como un selector implacable. Como resultado :is() y :where() son los únicos selectores relativos indulgentes del grupo.

Esto significa :has() se comporta mucho más como un selector compuesto.De acuerdo a especificaciones CSSpor razones heredadas, el comportamiento general del selector compuesto es el siguiente si algún selector de la lista no es válido, la lista completa de selectores no es válidalo que hace que se descarte todo el conjunto de reglas.

/* This doesn't do anything because `::-scoobydoo`
   is an invalid selector */
a, a::-scoobydoo {
  color: green;
}

Lo mismo va para :has()Cualquier selector no válido en su lista de argumentos invalidará todo lo demás en la lista. Entonces, este ejemplo que vimos antes:

/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }

...no hará absolutamente nada Los tres selectores en la lista de argumentos no son válidos debido a esta invalidación ::scoobydoo selector. Es una especie de todo o nada.

Artículo Recomendado:  Escribir HTML, modo HTML (no modo XHTML) trucos CSS

Pero hay una solución para esto. No se olvide, :is() y :where() son indulgentes incluso si :has() no lo es Esto significa que podemos anidar cualquiera de los selectores :has() para obtener un comportamiento más indulgente:

p::has(:where(a, a::scoobydoo)) {
  color: green;
}

Así que si alguna vez preguntas :has() para trabajar como un selector "perdonador", intente anidar :is() o :where() dentro de eso.

📑 Aquí podrás encontrar 👇

La lista de argumentos acepta selectores complejos.

A selector complejo contiene uno o más selectores compuestos (p. ej. a.fancy-link) y combinadores (p. ej. >, +, ~La lista de argumentos para :has() acepta estos selectores completos y se puede utilizar para identificar relaciones entre varios elementos.

<relative-selector> = <combinator>? <complex-selector>

Aquí hay un ejemplo de un selector relativo que contiene un selector complejo con combinador de niños (>Selecciona elementos con un .icon clase que son hijos directos de relaciones que tienen un .fancy-link clase y están en un :focus estado:

a.fancy-link:focus > .icon {
  /* Styles */
}

Este tipo de cosas pueden se utilizan directamente en :has() lista de argumentos:

p:has(a.fancy-link:focus > .icon) {
  /* Styles */
}

Pero en lugar de elegir .icon elementos que son hijos directos de .fancy-class conexiones que están en :focusdiseñamos párrafos que están enfocados .fancy-links con hijos directos que tienen .icon clase.

¡Uf, estoy tratando de decir eso tres veces más rápido! Puede ser útil ver un marcado de ejemplo que corresponda a:

eso generalmente no admite pseudo-selectores

yo digo esto :has() "generalmente" no admite otros pseudoelementos en sus argumentos, porque eso es exactamente lo que dice la especificación:

Nota: Los pseudoelementos generalmente se excluyen de :has() ya que muchos de ellos existen condicionalmente, en base al estilo de sus antepasados, lo que permite que sean consultados por :has() introducirá bucles.

De hecho, hay varios "pseudo-elementos permitidos" que están permitidos en el :has() lista de argumentos La especificación ofrece el siguiente ejemplo que muestra cómo :not() se usa junto :has():

/* Matches any <section> element that contains 
   anything that’s not a heading element. */
section:has(:not(h1, h2, h3, h4, h5, h6))

Jay Tompkins ofrece otro ejemplo Show :has()se puede usar para diseñar formularios en función de diferentes estados de entrada, como :valid, :invalidy :placeholder-shown:

label {
  color: var(--color);
}
input {
  border: 4px solid var(--color);
}

.form-group:has(:invalid) {
  --color: var(--invalid);
}

.form-group:has(:focus) {
  --color: var(--focus);
}

.form-group:has(:valid) {
  --color: var(--valid);
}

.form-group:has(:placeholder-shown) {
  --color: var(--blur);
}

No se puede insertar, pero admite encadenamiento.

Lo siento, no anidamos :has() dentro de :has() a la:

/* Nesting is a no-go */
.header-group:has(.subtitle:has(h2)) {
  /* Invalid! */
}

Esto crearía un bucle infinito donde la especificación se evalúa en otra evaluación. Sin embargo, esto le permite encadenar argumentos:

h2,
.subtitle {
  margin-block-end: 1.5rem;
}

/* Reduce spacing on header because the subtitle will handle it */
.header-group:has(h2):has(.subtitle) h2 {
  margin-block-end: 0.2rem;
}

El encadenamiento funciona como AND operación lógica porque ambas condiciones deben coincidir para que la regla de estilo tenga efecto. Supongamos que tiene una lista de .news-articles y los artículos que contiene están categorizados. Tal vez quiera aplicar ciertos estilos a la lista, pero solo si contiene artículos con .breaking-news y artículos con .featured-newspero déjelo sin cambios si solo uno o ninguno de los artículos coincide con estas clases.

Bueno, puedes atar dos :has() declaraciones para este estilo condicional:

.news-list:has(.featured-news):has(.breaking-news) { 
  /* Styles */ 
}

Este ejemplo es específico sólo para .news-list Si quisiéramos hacer coincidir algún elemento padre antiguo que :has() ambos .featured-news y .breaking-news clases de artículos, podríamos saltarnos .news-list total:

:has(.featured-news):has(.breaking-news) { 
  /* Styles */ 
}

Esto es más que un selector "principal"

Jay Tompkins lo llama un selector de "familia" que puede ser una descripción más apropiada, especialmente en lo que se refiere al último ejemplo que vimos. Veámoslo de nuevo:

.header-group:has(h2):has(.subtitle) h2 {
  margin-block-end: 0.2rem;
}

No solo seleccionamos un artículo con .header-group una clase que contiene un <h2> Estos son los poderes de elección de los padres que generalmente se atribuyen :has()Lo que seleccionamos es un elemento:

  • con .subtitle clase
  • este es un hijo de .header-group
  • y contiene un <h2> elemento en su interior.

Bien <h2> el hijo directo de .header-groupNo, es más como un nieto.

Combinatorio :has() con otros pseudo-selectores relacionales

Puedes combinar :has() con otros selectores de pseudoclases funcionales como :where(), :not() y :is().

Peinada :has() y :is()

Por ejemplo, puede verificar si alguno de los encabezados HTML tiene al menos una <a> elemento como descendiente:

:is(h1, h2, h3, h4, h5, h6):has(a) {
  color: blue;
}

/* is equivalent to: */
h1:has(a),
h2:has(a),
h3:has(a),
h4:has(a),
h5:has(a),
h6:has(a) {
  color: blue;
}

también puedes pasar :is() como argumento para :has()Imagínese si cambiamos el último ejemplo para que cada nivel de encabezado que contiene <a> elemento hijo o cualquier elemento hijo con .link clase seleccionada:

:is(h1, h2, h3, h4, h5, h6):has(:is(a, .link)) {
  color: blue;
}

Combinatorio :has() y :not()

Nosotros podemos usar :has() con :not() Supongamos que desea agregar un marco a un .card elemento si no contiene uno <img> elemento sucesor. Cosa segura:

.card:not(:has(img)) {
  border: 1px solid var(--my-amazing-color);
}

Esto comprueba que la tarjeta :has() cada imagen luego dice:

Oye, si no encuentras ninguna imagen, aplica estos estilos.

¿Quieres volverte más loco? Elijamos cada uno .post elemento para imágenes faltantes alt texto:

.post:has(img:not([alt])) {
  /* Styles */
}

¿Ves lo que hicimos aquí? Esta vez, :not() es en :has().Esto dice:

Oye, si encuentras alguna publicación que contenga una imagen sin texto alternativo, aplica esos estilos y gracias.

Esto se puede usar para depurar imágenes faltantes alt atributo:

Artículo Recomendado:  Compare Node JavaScript con JavaScript en el navegador trucos CSS

Aquí hay otro ejemplo que tomé prestado de vídeo de eric meierDigamos que desea seleccionar cada <div> que no tiene mas que <img> elementos.

div:not(:has(:not(img))) {
  /* Styles */
}

Lo que estamos diciendo aquí es:

Si encuentras un <div> y no hay nada más que una o más imágenes, ¡haz tu magia!

Estas son situaciones donde el orden importa

Observe cómo cambiar el orden de nuestros selectores cambia lo que seleccionan. Hablamos de la naturaleza implacable de:has()lista de argumentos, pero es aún menos indulgente en los ejemplos que hemos visto con esta combinación :has() con otros pseudo-selectores relacionales.

Veamos un ejemplo:

article:not(:has(img)) {
  /* Styles */
}

Esto coincide con cualquier <article> elementos que no contienen imágenes. Ahora cambiemos las cosas para que :has() viene antes :not():

article:has(:not(img)) {
  /* Styles */
}

Ahora emparejamos cada uno <article> elemento que contiene algo siempre y cuando no haya imágenes. El artículo debe tener un sucesor que coincida con este descendiente. puede ser cualquier cosa menos una imagen.

casos de uso

Separadores de migas de pan

Las rutas de navegación son una forma conveniente de mostrar en qué página se encuentra actualmente un usuario y dónde encaja esa página en el mapa del sitio. Por ejemplo, si está en una página "Acerca de", puede mostrar una lista que contiene un elemento con un enlace a la página de inicio y un elemento que simplemente muestra la página actual:

<ol class="breadcrumb">
  <li class="breadcrumb-item"><a href="https://css-tricks.com/">Home</a></li>
  <li class="breadcrumb-item current">About</li>
</ol>

Esto es genial. Pero, ¿y si queremos mostrarlo como una lista horizontal y ocultar los números de la lista? Fácil con CSS:

ol {
  display: flex;
  list-style: none;
}

¡Manten tu cabeza en alto! Ajustes list-style: none evita que Safari identifique el elemento como una lista.

Bien, pero ahora nos quedan dos elementos de la lista que ocurren uno dentro del otro. Podemos agregar un gap entre ellos, ya que estamos usando Flexbox:

ol {
  display: flex;
  gap: .5rem;
  list-style: none;
}

Eso ciertamente ayuda. Pero podemos hacer una discriminación más fuerte entre los dos elementos agregando un separador entre ellos. Nada de miedo:

.breadcrumb-item::after {
  content: "https://css-tricks.com/";
}

¡Pero espera! No necesitamos un delimitador después .current elemento porque siempre es el último en la lista y no hay nada después de él. aquí es donde :has() Podemos buscar a cualquier niño con .current clase usando el siguiente combinador hijo (~) para olerlo:

.breadcrumb-item:has(~ .current)::after {
  content: "https://css-tricks.com/";
}

¡Aquí!

Validación de formularios sin JavaScript

Como aprendimos antes, :has() no acepta pseudo-elementos, pero nos permite usar pseudo-clases. Podemos usar esto como una forma ligera de validación con la que normalmente tratamos en JavaScript.

Digamos que tenemos un formulario de registro de boletín que requiere un correo electrónico:

<form>  
  <label for="email-input">Add your pretty email:</label>
  <input id="email-input" type="email" required>
</form>

El correo electrónico es un campo obligatorio en este formulario. De lo contrario, ¡no hay nada que enviar! Tal vez podamos agregar un borde rojo a la entrada si el usuario ingresa una dirección de correo electrónico no válida:

form:has(input:invalid) {
  border: 1px solid red;
}

Intente ingresar una dirección de correo electrónico no válida, luego haga clic en el campo de contraseña de la pestaña.

Formateo de elementos completados en una lista de tareas pendientes

¿Ha intentado diseñar la etiqueta de la casilla de verificación cuando se marca la entrada? Ya sabes, como una aplicación de lista de tareas pendientes en la que completas los elementos de la lista marcando una casilla.

La estructura de su HTML podría verse así:

<form>
  <input id="example-checkbox" type="checkbox">
  <label for="example-checkbox">We need to target this when input is checked</label>
</form>

Aunque es mejor poner la etiqueta antes o envolverla alrededor del elemento de entrada para debido a la accesibilidaddebe colocar la etiqueta después de la entrada para poder seleccionar la etiqueta en función de la entrada check atributo.

Artículo Recomendado:  borde-imagen-ancho | Trucos CSS - Trucos CSS

Usando un combinador hermano siguiente (+), puede diseñar la etiqueta de esta manera:

/* When the input is checked, 
  style the label */
input:checked + label {
  color: green;
}

Cambiemos el marcado y creemos una etiqueta implícita poniendo la entrada dentro:

<form>  
  <label>
    <input type="checkbox">
    We need to target this when input is checked
  </label>
</form>

Antes no había forma de seleccionar esta etiqueta cuando se verificaba el inicio de sesión, pero ahora que tenemos :has() selector, tenemos plena potencia:

/* If a label has a checked input,
  style that label */
label:has(input:checked) {
  color: green;
}

Ahora volvamos al marcado ideal donde la etiqueta viene antes de la entrada:

<form> 
  <label for="example-checkbox">We need to target this when input is checked</label>
  <input id="example-checkbox" type="checkbox">
</form>

Todavía puedes usar :has() Como el selector anterior para seleccionar y diseñar la etiqueta explícita, manteniendo un marcado más accesible:

/* If a label has a checked input 
   that is it's next sibling, style 
   the label */
label:has(+ input:checked) {
  color: green;
}

Botón inteligente "Añadir al carrito".

Lo que pasa, aplicamos :has() al elemento principal o cuerpo de la página?

:root:has( /* Any condition */ ) {
  /* Styles */
}

Él :root es el documento de nivel superior el que controla todo lo que está debajo, ¿verdad? Si algo sucede en algún lugar más abajo en el árbol DOM, puede detectarlo y dar forma a otra rama DOM en consecuencia.

Supongamos que ejecuta un sitio de comercio electrónico y desea diseñar el botón "Agregar al carrito" cuando se agrega un producto al carrito. Esto es bastante común, ¿verdad? Sitios como Amazon hacen esto todo el tiempo para informar al usuario un artículo está correctamente en su carrito.

Imagina que esta es la estructura de nuestro HTML. El botón "Agregar al carrito" se encuentra en el <header> elemento y el producto está contenido en un <main> elemento.

Un diagrama que identifica dónde se encuentran los productos y botones en el árbol DOM.

Un ejemplo de marcado súper simplificado podría verse así:

<body>
  <header>
    <button class="cart-button">Add to cart</button>
  </header>
  <main> 
    <ul>
      <li class="p-item">Product</li>
      <li class="p-item is-selected">Product</li>
      <li class="p-item">Product</li>
    </ul>
  </main>
</body>

En CSS podemos comprobar si <body> :has() cualquier descendencia con ambos .p-item y .is-selected Una vez que esta condición es verdadera, .cart-button se puede seleccionar:

body:has(.p-item.is-selected) .cart-button {
  background-color: green;
}

Cambiar temas de color

Modo oscuro, Modo claro, Modo de alto contraste Dar a los usuarios la opción de personalizar el tema de color de un sitio puede ser una buena mejora de UX.

Digamos que en algún lugar profundo del documento tienes un <select> menú de usuario para seleccionar un tema de color:

<select>
  <option value="light">Light</option>
  <option value="dark">Dark</option>
  <option value="high-contrast">High-contrast</option>
</select>

Nosotros podemos usar :has() sobre <body> elemento y verifique <select> el menú está seleccionado <option>Así, si uno <option> contiene un cierto valor, podemos actualizar las propiedades personalizadas de CSS con diferentes valores de color para cambiar el tema de color actual:

body:has(option[value="dark"]:checked) {
  --primary-color: #e43;
  --surface-color: #1b1b1b;
  --text-color: #eee;
}

Algo está pasando de nuevo (usuario <select>es un <option> ) en algún lugar del árbol DOM y observe los cambios en el nivel superior del árbol (el <body>) y actualice los estilos en consecuencia (a través de propiedades personalizadas).

Estilo de un elemento basado en el número de niños

Aquí viene uno inteligente a través de Brahms Van Damme. :has() puede aplicar estilos en función del número de elementos secundarios en un contenedor principal.

Imagine que tiene un diseño con dos columnas. Si el número de elementos en la cuadrícula es os: 3, 5, 7, 9, etc., entonces le queda un espacio vacío en la cuadrícula después del último elemento.

Repare un diseño de dos columnas con un número impar de elementos secundarios donde el primer elemento secundario ocupa la primera fila.

Sería mejor si el primer elemento de la cuadrícula pudiera ocupar ambas columnas en la primera fila para evitar esto. Y para eso tendrás que comprobar si :last-child el elemento de cuadrícula también es un hijo impar:

/* If the last item in a grid is an odd-numbered child */
.grid-item:last-child:nth-child(odd) {
  /* Styles */
}

Esto se puede transferir a :has() una lista de argumentos para que podamos diseñar la cuadrícula :first-child por lo que ocupa toda la primera fila de la cuadrícula cuando :last-child es un número impar:

.grid:has(> .grid-item:last-child:nth-child(odd)) .grid-item:first-child {
  grid-column: 1 / -1;
}

Compatibilidad con navegador

Escritorio

Cromo Firefox ES DECIR Borde, final Safari
105 no no 105 15.4

Móvil/tableta

Android cromo android firefox Androide iOSSafari
108 no 108 15.4

Pruebas de soporte

Él @supports generalmente apoya :has() lo que significa que podemos verificar si el navegador lo admite y aplicar estilos condicionalmente según el resultado:

@supports(figure(:has(figcaption))) {
  /* Supported! */
}

Más información

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

https://kirin-mountainview.com/

https://www.bamboo-thai.com/