Nomi CSS definiti dall'autore e shadow DOM: nella specifica e nella pratica

I nomi CSS definiti dallo scrittore e lo shadow DOM dovrebbero funzionare insieme. Tuttavia, i browser non sono coerenti con la specifica, a volte tra di loro, e ogni nome CSS non è coerente in modo leggermente diverso.

Questo articolo descrive lo stato attuale del comportamento dei nomi CSS definiti dall'autore nei vari ambiti shadow, con la speranza che possa servire da guida per migliorare l'interoperabilità nel prossimo futuro.

Che cosa sono i nomi CSS definiti dall'autore?

I nomi CSS definiti dall'autore sono un meccanismo di sintassi CSS relativamente antico, originariamente introdotto per la regola @keyframes, che definisce un <keyframe-name> come un ident personalizzato o una stringa. Lo scopo di questo concetto è dichiarare qualcosa in una parte di un foglio di stile e farvi riferimento in un'altra.

/* "fade-in" is a CSS name, representing a set of keyframes */
@keyframes fade-in {
  from { opacity: 0 };
  to { opacity: 1 }
}

.card {
  /* "fade-in" is a reference to the above keyframes */
  animation-name: fade-in;
}

Altre funzionalità CSS che utilizzano i nomi CSS sono i caratteri, le dichiarazioni delle proprietà, le query dei contenitori e, più di recente, le transizioni di visualizzazione, il posizionamento dell'ancora e le animazioni basate sullo scorrimento. La seguente tabella non esaustiva include i nomi di cui Chrome controlla lo stato.

Funzionalità Dichiarazione del nome Riferimento nome
Fotogrammi chiave @keyframes animation-name
Caratteri @font-face { }
@font-palette-values
font-family
font-palette
Dichiarazioni relative alla proprietà @property
Qualsiasi dichiarazione di proprietà personalizzata non registrata
var()
Visualizza transizioni view-transition-name
view-transition-class
::view-transition-* elementi pseudo
Posizionamento dell'ancora anchor-name position-anchor
Animazione con scorrimento view-timeline-name
scroll-timeline-name
animation-timeline
Stili di elenco @counter-style list-style
Contatori counter-reset
counter-set
counter-increment
Query sui contenitori container-name @container
Pagina page @page

Come puoi vedere nella tabella, un nome CSS ha in genere un corrispondente riferimento CSS. Ad esempio, animation-name è un riferimento al nome @keyframes. I nomi CSS sono diversi da quelli definiti nel DOM, come gli attributi e i nomi dei tag, in quanto vengono dichiarati e poi richiamati nel contesto degli stile.

Correlazione dei nomi allo shadow DOM

Sebbene i nomi CSS siano progettati per creare relazioni tra parti diverse di un documento o di uno stile, il DOM ombra è progettato per fare il contrario. Incapsula le relazioni in modo che non vengano divulgate tra i componenti web che dovrebbero avere un proprio spazio dei nomi.

Combinando i nomi CSS e lo shadow DOM, l'esperienza di composizione dei componenti web dovrebbe sembrare abbastanza espressiva da essere flessibile, ma abbastanza limitata da essere stabile.

In teoria è una buona idea. In pratica, i browser non sono coerenti nel modo in cui i nomi CSS interagiscono con lo shadow DOM, sia tra le funzionalità nello stesso browser, nei vari browser, sia tra le funzionalità e la specifica.

Modalità di interazione tra nomi e shadow DOM

Per comprendere il problema, è utile capire come queste parti del CSS dovrebbero funzionare insieme in teoria.

La regola generale

La regola generale relativa al comportamento dei nomi CSS nei vari alberi ombre è definita nella specifica CSS Scoping Level 1. Riassumendo: un nome CSS è globale all'interno dell'ambito in cui è definito, il che significa che è possibile accedervi dagli alberi ombre discendenti, ma non da quelli di pari livello o predecessore. Tieni presente che questo è diverso dai nomi della piattaforma web come gli ID elemento, che sono incapsulati nello stesso ambito dell'albero.

Eccezione alla regola: @property

A differenza di altri nomi CSS, le proprietà CSS non sono incapsulate dal DOM ombra. Piuttosto, sono il mezzo comune per trasmettere parametri tra diversi alberi ombre. Questo rende speciale il descrittore @property: dovrebbe comportarsi come una dichiarazione di tipo document-globale che definisce il comportamento di una determinata proprietà con nome. Poiché le proprietà devono corrispondere tra gli alberi ombra, la mancata corrispondenza delle dichiarazioni delle proprietà genererebbe risultati imprevisti, quindi le dichiarazioni @property vengono specificate in modo da essere suddivise e risolte in base all'ordine del documento.

Come dovrebbe funzionare la regola con ::part

Le parti ombra espongono un elemento all'interno di un albero ombra al relativo albero principale. In questo modo, la struttura ad albero principale può accedere all'elemento e modificarne lo stile utilizzando l'elemento ::part.

Poiché ::part consente a due ambiti dell'albero di applicare stili allo stesso elemento, viene specificato il seguente ordine di applicazione in cascata:

  1. Innanzitutto, controlla lo stile all'interno del contesto shadow. Si tratta dello stile "predefinito" del componente.
  2. poi applica lo stile esterno come definito in ::part. Si tratta dello stile "personalizzato" della parte.
  3. Poi, applica eventuali stili interni definiti insieme a !important. Ciò consente a un elemento personalizzato di dichiarare che una determinata proprietà di una determinata parte non è personalizzabile da ::part.

Ciò significa che non è possibile fare riferimento ai nomi all'interno dello shadow DOM da un ::part, in quanto ::part è uno stile basato sull'host anziché uno stile basato sullo shadow. Ad esempio:

// inside the shadow DOM:
@keyframes fade-in {
  from { opacity: 0}
}

// This shouldn't work!
// The host style shouldn't know the name "fade-in"
::part(slider) {
  animation-name: fade-in;  
}

Come dovrebbe funzionare la regola con gli stili in linea

A differenza di ::part, gli stili incorporati con l'attributo style o quelli che impostano lo stile tramite script in modo programmatico hanno come ambito l'elemento. Questo perché per applicare uno stile a un elemento devi accedere all'handle dell'elemento e quindi all'elemento radice ombra stesso.

Come i nomi CSS e lo shadow DOM funzionano insieme nella realtà

Sebbene le regole precedenti siano ben definite e coerenti, le implementazioni attuali non sempre riflettono questo aspetto. In pratica, @property funziona in modo diverso rispetto alle specifiche in modo coerente tra i browser e la maggior parte delle altre funzionalità presenta bug aperti (alcune non sono ancora state rilasciate, quindi c'è tempo per correggerli).

Per testare e dimostrare come funzionano queste funzionalità nella pratica, abbiamo creato la seguente pagina: https://css-names-in-the-shadow.glitch.me/. Questa pagina contiene diversi iframe, ciascuno incentrato su una delle funzionalità e i sei scenari di test:

  • Riferimento esterno a un nome esterno: non è coinvolto il DOM ombra, quindi dovrebbe funzionare.
  • Riferimento esterno a un nome interno: non dovrebbe funzionare, perché ciò significherebbe che il nome definito nel contesto shadow è trapelato.
  • Riferimento interno al nome esterno: dovrebbe funzionare, poiché i nomi a livello di albero vengono ereditati dalle radici shadow.
  • Riferimento interno al nome interno: dovrebbe funzionare, poiché sia il nome del riferimento sia il nome dell'elemento di riferimento si trovano nello stesso ambito.
  • Riferimento a ::part al nome esterno: dovrebbe funzionare, poiché sia ::part che il nome sono dichiarati nello stesso ambito.
  • ::part riferimento al nome interno: non dovrebbe funzionare, poiché l'ambito esterno non dovrebbe acquisire informazioni sui nomi dichiarati all'interno del DOM ombra.

@keyframes

Come definito nella specifica, dovresti essere in grado di fare riferimento ai nomi dei fotogrammi chiave dall'interno di un elemento shadow root, purché la regola at @keyframes sia in un ambito antenato. In pratica, nessun browser implementa questo comportamento e alle definizioni dei fotogrammi chiave si può fare riferimento solo nell'ambito in cui sono definite. Consulta Issue 10540.

@property

Come definito nella specifica, qualsiasi dichiarazione di @property verrà appiattita all'ambito del documento. Tuttavia, al momento, in tutti i browser puoi dichiarare @property solo nell'ambito del documento e le dichiarazioni @property all'interno delle radici shadow vengono ignorate.
Leggi il problema 10541.

Bug specifici del browser

Le altre funzionalità non mostrano un comportamento coerente tra i vari browser:

  • @font-face viene appiattito nell'ambito principale in Safari.
  • Chromium non consente l'eredità delle regole anchor-name in un elemento shadow root
  • scroll-timeline-name e view-timeline-name non sono inclusi correttamente in ::part (anche in Chromium).
  • Nessun browser consente di dichiarare @font-palette-values in una radice ombra.
  • view-transition-class può essere definito all'interno di un elemento shadow root (la transizione stessa si trova all'esterno dell'elemento shadow root).
  • Firefox consente a ::part di accedere ai nomi delle ombre interne (query dei contenitori,keyframe).
  • Firefox e Safari non rispettano @counter-style in un elemento shadow root.

Tieni presente che counter-reset, counter-set, counter-increment hanno regole leggermente diverse perché sono nomi impliciti e le proprietà CSS dichiarate hanno un insieme di regole stabilito e ben testato.

Conclusione

La cattiva notizia è che quando esamini l'istantanea dello stato di interoperabilità attuale per quanto riguarda i nomi CSS e lo shadow DOM, l'esperienza è incoerente e buggy. Nessuna delle funzionalità che abbiamo esaminato qui si comporta in modo coerente su tutti i browser e in base alle specifiche. La buona notizia è che il delta per rendere l'esperienza coerente è un elenco finito di bug e problemi relativi alle specifiche. Risolviamo il problema. Nel frattempo, questa panoramica può aiutarti se hai difficoltà con le incoerenze descritte in questo articolo.