Sin dall'inizio (in termini CSS), lavoriamo a cascata in vari sensi. I nostri stili compongono un "Cascading Style Sheet". E anche i nostri selettori si attivano a cascata. Possono essere ruotate. Nella maggior parte dei casi, i valori sono scesi. Ma mai verso l'alto. Per anni abbiamo fantasticato su un "Selettore genitore". E ora è in arrivo! A forma di pseudo-selettore :has()
.
La pseudo-classe CSS :has()
rappresenta un elemento se uno o più selettori trasmessi come parametri corrisponde ad almeno un elemento.
Ma è più di un selettore "padre". È un bel modo per commercializzarlo. A volte non è così accattivante il selettore dell'"ambiente condizionale". Ma non ha lo stesso suono. Che ne dici del selettore "famiglia"?
Supporto dei browser
Prima di proseguire, vale la pena menzionare il supporto dei browser. Non è ancora lì. Ma ci si avvicina sempre di più. Firefox non supporta ancora, ma è in arrivo. Tuttavia è già in Safari e dovrebbe essere rilasciato in Chromium 105. Tutte le demo in questo articolo ti indicheranno se non sono supportate nel browser utilizzato.
Come utilizzare :ha
Come si presenta? Considera il seguente codice HTML con due elementi di pari livello con la classe everybody
. Come selezioneresti quella con un discendente con la classe a-good-time
?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
Puoi farlo con :has()
con il seguente CSS.
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
Viene selezionata la prima istanza di .everybody
e viene applicato un animation
.
In questo esempio, l'elemento con la classe everybody
è il target. La condizione ha un discendente con la classe a-good-time
.
<target>:has(<condition>) { <styles> }
Ma puoi andare oltre, perché :has()
ti offre molte opportunità. Anche quelli probabilmente non ancora scoperti. Prendi in considerazione alcuni di questi.
Seleziona elementi figure
che hanno un valore figcaption
diretto.
css
figure:has(> figcaption) { ... }
Seleziona anchor
che non hanno un discendente SVG diretto
css
a:not(:has(> svg)) { ... }
Seleziona label
che hanno un fratello input
diretto. Stai andando di lato!
css
label:has(+ input) { … }
Seleziona article
dove un discendente img
non ha testo alt
css
article:has(img:not([alt])) { … }
Seleziona documentElement
dove è presente uno stato nel DOM
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
Seleziona il contenitore del layout con un numero dispari di elementi secondari
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
Seleziona tutti gli elementi di una griglia a cui non è stato passato il mouse
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Seleziona il contenitore che contiene un elemento personalizzato <todo-list>
css
main:has(todo-list) { ... }
Seleziona ogni singolo elemento a
all'interno di un paragrafo con un elemento di pari
/2 diretto.article
hr
css
p:has(+ hr) a:only-child { … }
css
article:has(>h1):has(>h2) { … }
Seleziona un article
in cui un titolo è seguito da un sottotitolo
css
article:has(> h1 + h2) { … }
Seleziona :root
quando vengono attivati gli stati interattivi
css
:root:has(a:hover) { … }
Seleziona il paragrafo che segue un figure
che non ha una figcaption
css
figure:not(:has(figcaption)) + p { … }
Quali casi d'uso interessanti ti vengono in mente per :has()
? La cosa affascinante qui è che ti ha incoraggiato a rompere il tuo modello mentale. Ti fa pensare: "Potrei affrontare questi stili in un modo diverso?".
Esempi
Diamo un'occhiata ad alcuni esempi di come possiamo utilizzarlo.
Carte
Avvia una demo classica delle carte. Potremmo mostrare qualsiasi informazione nella nostra scheda, ad esempio un titolo, un sottotitolo o alcuni contenuti multimediali. Questa è la carta di base.
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
Cosa succede quando si vogliono introdurre alcuni contenuti multimediali? Per questa struttura, la scheda potrebbe essere suddivisa in due colonne. Prima potresti creare una nuova classe per rappresentare questo comportamento, ad esempio card--with-media
o card--two-columns
. Questi nomi di classi non solo sono difficili da evocare, ma diventano anche difficili da mantenere e ricordare.
Con :has()
, puoi rilevare che la scheda contiene contenuti multimediali e intraprendere le azioni appropriate. Non sono necessari i nomi delle classi di modifica.
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
E non c'è bisogno di lasciarlo lì. Puoi usare la tua creatività. In che modo una scheda che mostra contenuti "in primo piano" potrebbe adattarsi a un layout? Questo CSS applica una scheda in primo piano a tutta la larghezza del layout e la posiziona all'inizio di una griglia.
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
Cosa succede se una scheda in primo piano con un banner ondeggia per attirare l'attenzione?
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
Tante possibilità.
Moduli
E i moduli? Sono noti per lo stile difficile. Ne è un esempio l'applicazione di stili agli input e alle relative etichette. Ad esempio, come viene indicato che un campo è valido? Con :has()
, è molto più facile. Possiamo agganciare le pseudo-classi del modulo pertinenti, ad esempio :valid
e :invalid
.
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
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);
}
Prova in questo esempio: prova a inserire valori validi e non validi e a concentrarti e disattivare lo stato attivo.
Puoi anche utilizzare :has()
per mostrare e nascondere il messaggio di errore relativo a un campo. Aggiungi un messaggio di errore al nostro gruppo di campi "Email".
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
Per impostazione predefinita, il messaggio di errore viene nascosto.
.form-group__error {
display: none;
}
Tuttavia, quando il campo diventa :invalid
e non è attivo, puoi mostrare il messaggio senza bisogno di nomi di classe aggiuntivi.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Non c'è motivo per cui non potresti aggiungere un pizzico di bizzarro stile quando gli utenti interagiscono con il modulo. Considera questo esempio. Controlla quando inserisci un valore valido per la micro-interazione. Un valore :invalid
causerà la vibrazione del gruppo di moduli. Tuttavia, solo se l'utente non ha preferenze di movimento.
Contenuti
Ne abbiamo parlato negli esempi di codice. Ma come potresti utilizzare :has()
nel flusso dei documenti? Vengono suggerite idee su come potremmo definire uno stile tipografico in base ai contenuti multimediali, ad esempio.
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
Questo esempio contiene figure. Quando non hanno figcaption
, galleggiano all'interno dei contenuti. Quando è presente un figcaption
, questo occupa l'intera larghezza e ha un margine extra.
Reazione allo Stato
Che ne dici di rendere i tuoi stili reattivi ad alcuni stati nel nostro markup? Considera un esempio con la barra di navigazione scorrevole "classica". Se è presente un pulsante che attiva/disattiva l'apertura del menu di navigazione, potrebbe essere utilizzato l'attributo aria-expanded
. Puoi usare JavaScript per aggiornare gli attributi appropriati. Quando aria-expanded
è true
, usa :has()
per rilevarlo e aggiornare gli stili per la navigazione scorrevole. JavaScript fa la sua parte e il CSS può fare quello che vuole con queste informazioni. Non c'è bisogno di spostare il markup del markup o aggiungere nomi di classi extra e così via (nota: questo non è un esempio pronto per la produzione).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Può :aiutare a evitare errori dell'utente?
Cos'hanno in comune tutti questi esempi? A parte il fatto che mostrano modi per utilizzare :has()
, nessuno di loro ha richiesto di modificare i nomi delle classi. Ognuno di questi ha inserito nuovi contenuti e aggiornato un attributo. Questo è un grande vantaggio di :has()
, in quanto può contribuire a ridurre l'errore dell'utente. Con :has()
il CSS è in grado di assumersi la responsabilità di adeguare le modifiche nel DOM. Non devi destreggiarti tra i nomi delle classi in JavaScript, riducendo il rischio di errori dello sviluppatore. Ci siamo passati tutti quando facciamo un errore di battitura del nome di una classe e dobbiamo ricorrere a mantenimento in Object
ricerche.
È un pensiero interessante e ci porta a un markup più chiaro e a meno codice? Meno JavaScript, dato che non stiamo apportando altre modifiche a JavaScript. Meno HTML poiché non hai più bisogno di corsi come card card--has-media
e così via.
Pensare fuori dagli schemi
Come accennato in precedenza, :has()
ti incoraggia a violare il modello mentale. Ti offre l'opportunità di provare cose diverse. Per provare a superare i limiti, puoi creare le meccaniche di gioco utilizzando solo il CSS. Ad esempio, puoi creare un meccanico basato su passaggi con moduli e CSS.
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
E questo apre possibilità interessanti. Potresti utilizzarlo per attraversare una modulo con le trasformazioni. Tieni presente che questa demo viene visualizzata al meglio in una scheda separata del browser.
E per divertimento, che ne dici del classico gioco Buzz Wired? Il meccanismo è più facile da creare con :has()
. Se passi il cavo sopra, significa che la partita è finita. Sì, possiamo creare alcune di queste meccaniche di gioco con elementi come i combinatori di fratelli (+
e ~
). Tuttavia, :has()
è un modo per ottenere gli stessi risultati senza dover utilizzare "trucchi" di markup interessanti. Tieni presente che questa demo viene visualizzata al meglio in una scheda separata del browser.
Anche se a breve non li metterai in produzione, mettono in evidenza i modi in cui puoi utilizzare la funzione primitiva. Ad esempio, è possibile concatenare un :has()
.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
Prestazioni e limitazioni
Prima di finire, cosa non puoi fare con :has()
? Esistono alcune limitazioni con :has()
. Quelli principali sono dovuti agli hit del rendimento.
- Non puoi
:has()
un:has()
. Puoi però concatenare un:has()
.css :has(.a:has(.b)) { … }
- Nessun utilizzo di pseudo elementi all'interno di
:has()
css :has(::after) { … } :has(::first-letter) { … }
- Limita l'utilizzo di
:has()
all'interno di pseudo pseudo che accettano solo selettori composticss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Limita l'utilizzo di
:has()
dopo lo pseudo elementocss ::part(foo):has(:focus) { … }
- L'utilizzo di
:visited
sarà sempre falsocss :has(:visited) { … }
Per le metriche sul rendimento effettive relative a :has()
, consulta questo Glitch. Ringraziamo Byungwoo per aver condiviso queste informazioni e dettagli sull'implementazione.
È tutto!
Preparati per :has()
. Parlane con i tuoi amici e condividi questo post: il modo in cui ci affidiamo a CSS sarà una svolta assoluta.
Tutte le demo sono disponibili in questa colllezione CodePen.