Data di pubblicazione: 8 ottobre 2024
Per risolvere alcuni strani problemi di nidificazione del CSS, il gruppo di lavoro CSS ha deciso di aggiungere l'interfaccia CSSNestedDeclarations
alla specifica di nidificazione del CSS. Con questa aggiunta, le dichiarazioni che seguono le regole di stile non vengono più spostate verso l'alto, tra altri miglioramenti.
Queste modifiche sono disponibili in Chrome dalla versione 130 e sono pronte per essere testate in Firefox Nightly 132 e Safari Technology Preview 204.
Supporto dei browser
Il problema con l'annidamento CSS senza CSSNestedDeclarations
Uno dei problemi con l'annidamento CSS è che, in origine, lo snippet seguente non funziona come potresti inizialmente aspettarti:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Osservando il codice, si presume che l'elemento <div class=foo>
abbia un green
background-color
perché la dichiarazione background-color: green;
è l'ultima. Non è così in Chrome prima della versione 130. In queste versioni, che non supportano CSSNestedDeclarations
, il background-color
dell'elemento è red
.
Dopo l'analisi della regola effettiva, Chrome prima della versione 130 utilizza quanto segue:
.foo {
width: fit-content;
background-color: green;
@media screen {
& {
background-color: red;
}
}
}
Il CSS dopo l'analisi ha subito due modifiche:
- Il
background-color: green;
è stato spostato verso l'alto per unirsi alle altre due dichiarazioni. - Il
CSSMediaRule
nidificato è stato riscritto per racchiudere le relative dichiarazioni in unCSSStyleRule
aggiuntivo utilizzando il selettore&
.
Un'altra modifica tipica che vedresti in questo caso è l'eliminazione delle proprietà del parser che non supporta.
Puoi controllare il "CSS dopo l'analisi" leggendo cssText
da CSSStyleRule
.
Prova tu stesso questo playground interattivo:
Perché questo CSS è stato riscritto?
Per capire perché si è verificata questa riscrittura interna, devi capire come questo CSSStyleRule
viene rappresentato nel CSS Object Model (CSSOM).
In Chrome prima della versione 130, lo snippet CSS condiviso in precedenza viene serializzato nel seguente modo:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Di tutte le proprietà di un CSSStyleRule
, in questo caso sono pertinenti le seguenti due:
- La proprietà
style
, che è un'istanzaCSSStyleDeclaration
che rappresenta le dichiarazioni. - La proprietà
cssRules
, che è un elementoCSSRuleList
che contiene tutti gli oggettiCSSRule
nidificati.
Poiché tutte le dichiarazioni dello snippet CSS finiscono nella proprietà style
di CSStyleRule
, si verifica una perdita di informazioni. Quando esamini la proprietà style
, non è chiaro che background-color: green
sia stato dichiarato dopo CSSMediaRule
nidificato.
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ …
Questo è un problema, perché per il corretto funzionamento di un motore CSS è necessario distinguere le proprietà che appaiono all'inizio dei contenuti di una regola di stile da quelle che appaiono intersecate con altre regole.
Le dichiarazioni all'interno di CSSMediaRule
vengono improvvisamente incluse in un elemento CSSStyleRule
: questo perché CSSMediaRule
non è stato progettato per contenere dichiarazioni.
Poiché CSSMediaRule
può contenere regole nidificate, accessibili tramite la relativa proprietà cssRules
, le dichiarazioni vengono automaticamente racchiuse in un CSSStyleRule
.
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Come risolvere il problema?
Il gruppo di lavoro CSS ha esaminato diverse opzioni per risolvere il problema.
Una delle soluzioni suggerite era racchiudere tutte le dichiarazioni bare in un CSSStyleRule
nidificato con il selettore di nidificazione (&
). Questa idea è stata scartata per vari motivi, tra cui i seguenti effetti collaterali indesiderati della desugarizzazione di &
in :is(…)
:
- Ha un effetto sulla specificità. Questo perché
:is()
assume la specificità del suo argomento più specifico. - Non funziona bene con gli pseudo-elementi nel selettore esterno originale. Questo accade perché
:is()
non accetta pseudo-elementi nell'argomento dell'elenco di selettori.
Prendi ad esempio quanto segue:
#foo, .foo, .foo::before {
width: fit-content;
background-color: red;
@media screen {
background-color: green;
}
}
Dopo l'analisi, lo snippet diventa questo in Chrome prima della versione 130:
#foo,
.foo,
.foo::before {
width: fit-content;
background-color: red;
@media screen {
& {
background-color: green;
}
}
}
Si tratta di un problema perché il CSSRule
nidificato con il selettore &
:
- Appiattisce a
:is(#foo, .foo)
, eliminando.foo::before
dall'elenco del selettore. - Ha una specificità di
(1,0,0)
che ne rende più difficile la sovrascrittura in un secondo momento.
Per verificare, controlla a cosa viene serializzata la regola:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "#foo, .foo, .foo::before"
.resolvedSelectorText = "#foo, .foo, .foo::before"
.specificity = (1,0,0),(0,1,0),(0,1,1)
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: red
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
.specificity = (1,0,0)
.style (CSSStyleDeclaration, 1) =
- background-color: green
Visivamente, significa anche che il background-color
di .foo::before
è red
anziché green
.
Un altro approccio esaminato dal gruppo di lavoro CSS è stato quello di racchiudere tutte le dichiarazioni nidificate in una regola @nest
. Questa richiesta è stata ignorata a causa del peggioramento dell'esperienza dello sviluppatore che ne sarebbe derivato.
Presentazione dell'interfaccia di CSSNestedDeclarations
La soluzione scelta dal gruppo di lavoro CSS è l'introduzione della regola delle dichiarazioni nidificate.
Questa regola di dichiarazioni nidificate è implementata in Chrome a partire dalla versione 130.
Supporto dei browser
L'introduzione della regola delle dichiarazioni nidificate modifica il parser CSS in modo che inserisca automaticamente le dichiarazioni consecutive nidificate direttamente in un'istanza CSSNestedDeclarations
. Se serializzata, questa istanza CSSNestedDeclarations
finisce nella proprietà cssRules
di CSSStyleRule
.
Prendiamo di nuovo il seguente CSSStyleRule
come esempio:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Se viene serializzato in Chrome 130 o versioni successive, ha il seguente aspetto:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = (0,1,0)
.style (CSSStyleDeclaration, 1) =
- width: fit-content
.cssRules (CSSRuleList, 2) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: red
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: green
Poiché la regola CSSNestedDeclarations
termina in CSSRuleList
, l'analizzatore è in grado di mantenere la posizione della dichiarazione background-color: green
: dopo la dichiarazione background-color: red
(che fa parte di CSSMediaRule
).
Inoltre, la presenza di un'istanza CSSNestedDeclarations
non introduce nessuno dei cattivi effetti collaterali delle altre potenziali soluzioni, ora scartate, causate: la regola delle dichiarazioni nidificate corrisponde esattamente agli stessi elementi e pseudo-elementi della regola di stile padre, con lo stesso comportamento di specificità.
A riprova di ciò, ti chiedo di leggere il cssText
del CSSStyleRule
. Grazie alla regola delle dichiarazioni nidificate, è uguale al CSS di input:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Cosa comporta tutto ciò per te
Ciò significa che l'annidamento CSS è stato notevolmente migliorato a partire da Chrome 130. Tuttavia, significa anche che potresti dover rivedere parte del codice se intercalassi dichiarazioni semplici con regole nidificate.
Prendi l'esempio seguente che utilizza la meravigliosa @starting-style
/* This does not work in Chrome 130 */
#mypopover:popover-open {
@starting-style {
opacity: 0;
scale: 0.5;
}
opacity: 1;
scale: 1;
}
Prima di Chrome 130, queste dichiarazioni venivano sollevate. Le dichiarazioni opacity: 1;
e scale: 1;
verranno inserite in CSSStyleRule.style
, seguite da un CSSStartingStyleRule
(che rappresenta la regola @starting-style
) in CSSStyleRule.cssRules
.
A partire da Chrome 130, le dichiarazioni non vengono più sollevate e ti ritrovi con due oggetti CSSRule
nidificati in CSSStyleRule.cssRules
. In ordine: un CSSStartingStyleRule
(che rappresenta la regola @starting-style
) e un CSSNestedDeclarations
contenente le dichiarazioni opacity: 1; scale: 1;
.
A causa di questo comportamento modificato, le dichiarazioni @starting-style
vengono sovrascritte da quelle contenute nell'istanza CSSNestedDeclarations
, rimuovendo così l'animazione di entrata.
Per correggere il codice, assicurati che il blocco @starting-style
venga dopo le normali dichiarazioni. In questo modo:
/* This works in Chrome 130 */
#mypopover:popover-open {
opacity: 1;
scale: 1;
@starting-style {
opacity: 0;
scale: 0.5;
}
}
Se mantieni le dichiarazioni nidificate sopra le regole nidificate quando utilizzi il nesting CSS, il tuo codice funziona per lo più bene con tutte le versioni di tutti i browser che supportano il nesting CSS.
Infine, se vuoi funzionalità per rilevare la disponibilità di CSSNestedDeclarations
, puoi utilizzare il seguente snippet JavaScript:
if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
// CSSNestedDeclarations is not available
}