CSS e stili
Questo articolo illustra altre fantastiche funzionalità che puoi utilizzare con Shadow DOM. Si basa sui concetti discussi in Shadow DOM 101. Se cerchi un'introduzione, consulta questo articolo.
Introduzione
Ammettiamolo. Il markup non stilizzato non è molto accattivante. Per fortuna, il team di sviluppatori di Web Components lo aveva previsto e non ci ha lasciato in sospeso. Il modulo di ambito CSS definisce molte opzioni per applicare stili ai contenuti in un albero ombra.
Incapsulamento di stili
Una delle funzionalità principali di Shadow DOM è il confine ombra. Ha molte proprietà interessanti, ma una delle migliori è che fornisce l'incapsulamento degli stili senza costi. In altre parole:
<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
`;
</script>
Esistono due osservazioni interessanti su questa demo:
- In questa pagina sono presenti altri elementi h3, ma l'unico che corrisponde al selettore h3 e che quindi ha lo stile rosso è quello in ShadowRoot. Anche in questo caso, gli stili basati sugli ambiti sono predefiniti.
- Le altre regole di stile definite in questa pagina che hanno come target gli elementi h3 non si applicano ai miei contenuti. Questo perché i selettori non attraversano il confine dell'ombra.
Morale della storia? Abbiamo l'encapsulamento dello stile dal mondo esterno. Grazie a Shadow DOM.
Aggiunta di stili all'elemento host
:host
ti consente di selezionare e applicare uno stile all'elemento che ospita un albero ombra:
<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
<style>
:host {
text-transform: uppercase;
}
</style>
<content></content>
`;
</script>
Un aspetto da tenere presente è che le regole nella pagina principale hanno una specificità maggiore rispetto alle regole :host
definite nell'elemento, ma una specificità inferiore rispetto a un attributo style
definito nell'elemento host. In questo modo gli utenti possono ignorare lo stile dall'esterno.
:host
funziona anche solo nel contesto di un ShadowRoot, quindi non puoi utilizzarlo al di fuori di Shadow DOM.
La forma funzionale di :host(<selector>)
consente di scegliere come target l'elemento host se corrisponde a un <selector>
.
Esempio: corrispondi solo se l'elemento stesso ha la classe .different
(ad es. <x-foo class="different"></x-foo>
):
:host(.different) {
...
}
Reagire agli stati utente
Un caso d'uso comune per :host
è quando crei un elemento personalizzato e vuoi reagire a diversi stati utente (:hover, :focus, :active e così via).
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
Applicare un tema a un elemento
L'pseudoclasse :host-context(<selector>)
corrisponde all'elemento host se l'elemento stesso o uno dei suoi antenati corrisponde a <selector>
.
Un uso comune di :host-context()
è per applicare un tema a un elemento in base agli elementi circostanti. Ad esempio, molti applicano un tema applicando una classe a <html>
o <body>
:
<body class="different">
<x-foo></x-foo>
</body>
Puoi :host-context(.different)
applicare uno stile a <x-foo>
quando è un discendente di un elemento con la classe .different
:
:host-context(.different) {
color: red;
}
In questo modo puoi incapsulare le regole di stile nello shadow DOM di un elemento per applicare uno stile unico in base al contesto.
Supporta più tipi di host da un'unica radice ombra
Un altro utilizzo di :host
è se stai creando una libreria di temi e vuoi supportare lo stile di molti tipi di elementi host all'interno dello stesso DOM ombra.
:host(x-foo) {
/* Applies if the host is a <x-foo> element.*/
}
:host(x-foo:host) {
/* Same as above. Applies if the host is a <x-foo> element. */
}
:host(div) {
/* Applies if the host element is a <div>. */
}
Applicazione di stili agli elementi interni di Shadow DOM dall'esterno
Lo pseudo-elemento ::shadow
e il combinatore /deep/
sono come avere una Spada Vorpal dell'autorità CSS.
Consentono di attraversare il confine dello Shadow DOM per applicare stili agli elementi all'interno degli alberi shadow.
L'elemento pseudo ::shadow
Se un elemento ha almeno un albero ombra, lo pseudo-elemento ::shadow
corrisponde all'elemento radice ombra stesso.
Ti consente di scrivere selettori che stilano i nodi interni al DOM ombra di un elemento.
Ad esempio, se un elemento ospita un'origine ombreggiata, puoi scrivere #host::shadow span {}
per applicare lo stile a tutti gli elementi all'interno della relativa struttura ad albero ombreggiata.
<style>
#host::shadow span {
color: red;
}
</style>
<div id="host">
<span>Light DOM</span>
</div>
<script>
var host = document.querySelector('div');
var root = host.createShadowRoot();
root.innerHTML = `
<span>Shadow DOM</span>
<content></content>
`;
</script>
Esempio (elementi personalizzati): <x-tabs>
ha <x-panel>
elementi secondari nel proprio shadow DOM. Ogni riquadro ospita il proprio albero ombra contenente intestazioni h2
. Per applicare uno stile a queste intestazioni della pagina principale, puoi scrivere:
x-tabs::shadow x-panel::shadow h2 {
...
}
Il combinatore /deep/
Il combinatore /deep/
è simile a ::shadow
, ma più potente. Ignora completamente tutti i confini delle ombre e attraversa un numero qualsiasi di alberi delle ombre. In parole semplici, /deep/
ti consente di esaminare in dettaglio un elemento e scegliere come target qualsiasi nodo.
Il combinatore /deep/
è particolarmente utile nel mondo degli elementi personalizzati, dove è comune avere più livelli di DOM ombra. Alcuni esempi sono l'annidamento di una serie di elementi personalizzati (ognuno con il proprio albero ombra) o la creazione di un elemento che eredita da un altro utilizzando <shadow>
.
Esempio (elementi personalizzati): seleziona tutti gli elementi <x-panel>
che sono discendenti di
<x-tabs>
, in qualsiasi punto dell'albero:
x-tabs /deep/ x-panel {
...
}
Esempio: applica uno stile a tutti gli elementi con la classe .library-theme
in qualsiasi punto di un albero ombra:
body /deep/ .library-theme {
...
}
Utilizzo di querySelector()
Proprio come .shadowRoot
apre gli alberi ombre per la scansione del DOM, i combinatori aprono gli alberi ombre per la scansione dei selettori.
Invece di scrivere una catena nidificata di follia, puoi scrivere una singola istruzione:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
Applicazione di stili agli elementi nativi
I controlli HTML nativi sono difficili da stilizzare. Molte persone si arrendono e creano il proprio sito. Tuttavia, con ::shadow
e /deep/
, è possibile applicare stili a qualsiasi elemento della piattaforma web che utilizza Shadow DOM. Ottimi esempi sono i tipi <input>
e <video>
:
video /deep/ input[type="range"] {
background: hotpink;
}
Creazione di hook di stile
La personalizzazione è buona. In alcuni casi, potresti voler fare dei buchi nello scudo di stile di Shadow e creare agganci per consentire ad altri di applicare lo stile.
Utilizzo di ::shadow e /deep/
/deep/
è molto potente. Offre agli autori dei componenti un modo per designare singoli elementi come personalizzabili o una serie di elementi come personalizzabili in base al tema.
Esempio: applica uno stile a tutti gli elementi con la classe .library-theme
, ignorando tutti gli alberi ombre:
body /deep/ .library-theme {
...
}
Utilizzo di elementi pseudo personalizzati
Sia WebKit che
Firefox definiscono elementi pseudo per applicare stili a componenti interni degli elementi del browser nativo. Un buon esempio è input[type=range]
. Puoi applicare uno stile all'anteprima del cursore <span style="color:blue">blue</span>
impostando come target ::-webkit-slider-thumb
:
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
Analogamente a come i browser forniscono hook di stile per alcuni elementi interni, gli autori dei contenuti shadow DOM possono designare determinati elementi come personalizzabili da utenti esterni. Ciò avviene tramite gli pseudo elementi personalizzati.
Puoi designare un elemento come pseudo elemento personalizzato utilizzando l'attributo pseudo
.
Il valore o il nome deve avere il prefisso "x-". In questo modo viene creata un'associazione con l'elemento nell'albero ombra e gli estranei hanno una corsia designata per attraversare il confine dell'ombra.
Ecco un esempio di creazione di un widget di cursore personalizzato e di impostazione del colore blu per il cursore:
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<div>
<div pseudo="x-slider-thumb"></div>' +
</div>
`;
</script>
Utilizzo delle variabili CSS
Un modo efficace per creare hook di temi è tramite le variabili CSS. In sostanza, crea "segnaposto di stile" da compilare da parte di altri utenti.
Immagina un autore di elementi personalizzati che contrassegna i segnaposto delle variabili nel proprio shadow DOM. Uno per lo stile del carattere di un pulsante interno e un altro per il colore:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
Successivamente, l'utente che ha incorporato l'elemento li definisce in base alle proprie preferenze. Forse per abbinarlo al tema Comic Sans super cool della propria pagina:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
Grazie al modo in cui le variabili CSS vengono ereditate, tutto funziona alla perfezione. L'intero quadro è il seguente:
<style>
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
</style>
<div id="host">Host node</div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<style>
button {
color: var(--button-text-color, pink);
font-family: var(--button-font);
}
</style>
<content></content>
`;
</script>
Reimpostazione degli stili
Gli stili ereditabili come caratteri, colori e spaziatura delle righe continuano a influire sugli elementi
nello shadow DOM. Tuttavia, per la massima flessibilità, il DOM shadow ci fornisce la proprietà resetStyleInheritance
per controllare cosa succede al confine dell'ombra.
Pensalo come un modo per ricominciare da capo quando crei un nuovo componente.
resetStyleInheritance
false
- Valore predefinito. Le proprietà CSS ereditabili continuano a essere ereditate.true
: reimposta le proprietà ereditabili suinitial
al confine.
Di seguito è riportata una demo che mostra in che modo l'albero ombra è interessato dalla modifica di resetStyleInheritance
:
<div>
<h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
`;
</script>
<div class="demoarea" style="width:225px;">
<div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
<button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>
<script>
var container = document.querySelector('#style-ex-inheritance');
var root = container.createShadowRoot();
//root.resetStyleInheritance = false;
root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';
document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
root.resetStyleInheritance = !root.resetStyleInheritance;
e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
});
</script>
Comprendere .resetStyleInheritance
è un po' più complicato, principalmente perché influisce solo sulle proprietà CSS ereditabili. Indica che quando stai cercando una proprietà da ereditare, al confine tra la pagina e lo ShadowRoot, non ereditare i valori dall'host, ma utilizza il valore initial
(secondo le specifiche CSS).
Se hai dubbi su quali proprietà vengono ereditate in CSS, consulta questo pratico elenco o attiva/disattiva la casella di controllo "Mostra ereditato" nel riquadro Elemento.
Stile dei nodi distribuiti
I nodi distribuiti sono elementi che vengono visualizzati in un punto di inserzione (un elemento <content>
). L'elemento <content>
ti consente di selezionare i nodi dal DOM light e di visualizzarli in posizioni predefinite nel DOM shadow. Non sono logicamente nel DOM ombra, ma sono comunque elementi secondari dell'elemento host. I punti di inserzione sono solo un aspetto del rendering.
I nodi distribuiti mantengono gli stili del documento principale. In altre parole, le regole di stile della pagina principale continuano a essere applicate agli elementi, anche quando vengono visualizzati in un punto di inserzione. Ancora una volta, i nodi distribuiti sono ancora logicamente nel DOM della luce e non si spostano. Vengono semplicemente visualizzati altrove. Tuttavia, quando i nodi vengono distribuiti nello Shadow DOM, possono assumere stili aggiuntivi definiti all'interno dell'albero shadow.
Pseudoelemento ::content
I nodi distribuiti sono elementi secondari dell'elemento host, quindi come possiamo sceglierli come target all'interno del DOM ombra? La risposta è lo pseudo elemento CSS ::content
.
È un modo per scegliere come target i nodi light DOM che passano attraverso un punto di inserzione. Ad esempio:
::content > h3
applica stili a tutti i tag h3
che passano attraverso un punto di inserzione.
Vediamo un esempio:
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
<style>
h3 { color: red; }
content[select="h3"]::content > h3 {
color: green;
}
::content section p {
text-decoration: underline;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
`;
</script>
Reimpostazione degli stili nei punti di inserzione
Quando crei un ShadowRoot, hai la possibilità di reimpostare gli stili ereditati.
Anche i punti di inserzione <content>
e <shadow>
dispongono di questa opzione. Quando utilizzi
questi elementi, imposta .resetStyleInheritance
in JS o utilizza l'attributo booleano
reset-style-inheritance
sull'elemento stesso.
Per i punti di inserzione ShadowRoot o
<shadow>
:reset-style-inheritance
indica che le proprietà CSS ereditabili sono impostate suinitial
nell'host prima che colpiscano i contenuti in ombra. Questa posizione è nota come limite superiore.Per i punti di inserzione
<content>
:reset-style-inheritance
indica che le proprietà CSS ereditabili sono impostate suinitial
prima che gli elementi secondari dell'elemento host vengano distribuiti nel punto di inserzione. Questa posizione è nota come limite inferiore.
Conclusione
In qualità di autori di elementi personalizzati, abbiamo a disposizione moltissime opzioni per controllare l'aspetto dei nostri contenuti. Il DOM ombra costituisce la base di questo nuovo mondo.
Shadow DOM ci offre l'incapsulamento degli stili basato sugli ambiti e un mezzo per consentire l'accesso a tutto (o a una parte) del mondo esterno, a seconda delle nostre esigenze. Definiendo pseudo elementi personalizzati o includendo placeholder per variabili CSS, gli autori possono fornire a terze parti pratici hook di stile per personalizzare ulteriormente i propri contenuti. In definitiva, gli autori web hanno il controllo totale su come vengono rappresentati i loro contenuti.