CSS e stile
Questo articolo illustra altre cose straordinarie che puoi fare con Shadow DOM. Si basa sui concetti discussi in Shadow DOM 101. Se cerchi un'introduzione, consulta l'articolo.
Introduzione
Ammettiamolo. Non c'è niente di attraente nel markup senza stile. Fortunatamente, le menti brillanti dietro i componenti web hanno previsto questo problema e non ci hanno lasciato a mani vuote. Il modulo CSS Scoping definisce molte opzioni per applicare stili ai contenuti in uno shadow tree.
Incapsulamento dello stile
Una delle funzionalità principali di Shadow DOM è il shadow boundary. Ha molte proprietà interessanti, ma una delle migliori è che fornisce l'incapsulamento dello stile senza costi aggiuntivi. 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>
Ci sono due osservazioni interessanti su questa demo:
- In questa pagina sono presenti altri h3, ma l'unico che corrisponde al selettore h3, e quindi con stile rosso, è quello nello ShadowRoot. Anche in questo caso, gli stili con ambito sono predefiniti.
- Altre regole di stile definite in questa pagina che hanno come target gli h3 non influiscono sui miei contenuti. Questo perché i selettori non superano il confine dello shadow.
In sintesi, Abbiamo l'incapsulamento dello stile dal mondo esterno. Grazie, Shadow DOM!
Stile dell'elemento host
:host consente di selezionare e applicare uno stile all'elemento che ospita uno shadow tree:
<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 problema è 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.
Inoltre, :host funziona solo nel contesto di un elemento ShadowRoot, quindi non puoi utilizzarlo al di fuori dello Shadow DOM.
La forma funzionale di :host(<selector>) ti consente di scegliere come target l'elemento host se corrisponde a un <selector>.
Esempio: corrispondenza solo se l'elemento stesso ha la classe .different (ad es. <x-foo class="different"></x-foo>):
:host(.different) {
...
}
Reazione agli stati dell'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>
Applicazione di un tema a un elemento
La pseudoclasse :host-context(<selector>) corrisponde all'elemento host se questo o uno dei suoi predecessori corrisponde a <selector>.
Un uso comune di :host-context() è l'applicazione di un tema a un elemento in base ai suoi elementi circostanti. Ad esempio,
molte persone applicano i temi applicando una classe a <html> o <body>:
<body class="different">
<x-foo></x-foo>
</body>
Puoi utilizzare :host-context(.different) per applicare lo 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 univoco in base al contesto.
Supporta più tipi di host all'interno di una radice shadow
Un altro utilizzo di :host è la creazione di una libreria di temi e il supporto dello stile di molti tipi di elementi host all'interno dello stesso Shadow DOM.
: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>. */
}
Stile degli elementi interni di Shadow DOM dall'esterno
Lo pseudo-elemento ::shadow e il combinatore /deep/ sono come una spada vorpal dell'autorità CSS.
Consentono di superare il confine dello shadow DOM per applicare stili agli elementi all'interno degli shadow tree.
Pseudo-elemento ::shadow
Se un elemento ha almeno uno shadow tree, lo pseudo-elemento ::shadow corrisponde alla shadow root stessa.
Consente di scrivere selettori che applicano uno stile ai nodi interni allo shadow DOM di un elemento.
Ad esempio, se un elemento ospita una shadow root, puoi scrivere #host::shadow span {} per applicare lo stile a tutti gli span all'interno del suo shadow tree.
<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 suo Shadow DOM. Ogni pannello ospita il proprio shadow tree contenente le intestazioni h2. Per applicare uno stile a queste intestazioni dalla pagina principale, si potrebbe 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 shadow e attraversa un numero qualsiasi di alberi shadow. In parole povere, /deep/ ti consente di esaminare a fondo un elemento e scegliere come target qualsiasi nodo.
Il combinatore /deep/ è particolarmente utile nel mondo degli elementi personalizzati, dove è comune avere più livelli di Shadow DOM. Esempi tipici sono l'annidamento di un gruppo di elementi personalizzati (ciascuno con il proprio shadow tree) o la creazione di un elemento che eredita da un altro utilizzando <shadow>.
Esempio (elementi personalizzati): seleziona tutti gli elementi <x-panel> discendenti di <x-tabs>, in qualsiasi punto dell'albero:
x-tabs /deep/ x-panel {
...
}
Esempio: applica lo stile a tutti gli elementi con la classe .library-theme, in qualsiasi punto di uno shadow tree:
body /deep/ .library-theme {
...
}
Utilizzo di querySelector()
Proprio come .shadowRoot apre gli shadow tree per l'esplorazione del DOM, i combinatori aprono gli shadow tree per l'esplorazione 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');
Stile degli elementi nativi
I controlli HTML nativi sono difficili da personalizzare. Molte persone semplicemente si arrendono e creano la propria soluzione. Tuttavia, con ::shadow e /deep/, è possibile applicare uno stile 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 è un'ottima cosa. In alcuni casi, potresti voler creare dei varchi nello scudo di stile dello Shadow DOM e creare degli hook per consentire ad altri di applicare stili.
Utilizzo di ::shadow e /deep/
/deep/ è molto potente. Consente agli autori dei componenti di designare singoli elementi come personalizzabili o un insieme di elementi come tematizzabili.
Esempio: applica lo stile a tutti gli elementi che hanno la classe .library-theme, ignorando tutti gli shadow tree:
body /deep/ .library-theme {
...
}
Utilizzo di pseudo-elementi personalizzati
Sia WebKit che Firefox definiscono pseudo-elementi per applicare stili alle parti interne degli elementi nativi del browser. Un buon esempio è il input[type=range]. Puoi applicare uno stile al cursore <span style="color:blue">blue</span> scegliendo 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 in alcuni elementi interni, gli autori di contenuti Shadow DOM possono designare determinati elementi come applicabili a stili esterni. Questo avviene tramite pseudo-elementi personalizzati.
Puoi designare un elemento come pseudo-elemento personalizzato utilizzando l'attributo pseudo.
Il suo valore, o nome, deve avere il prefisso "x-". In questo modo viene creata un'associazione con quell'elemento nello shadow tree e viene fornita agli utenti esterni una corsia designata per attraversare il confine dello shadow.
Ecco un esempio di creazione di un widget di scorrimento personalizzato e di autorizzazione per applicare uno stile blu al 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 tematizzazione è utilizzare le variabili CSS. In sostanza, creano "segnaposto di stile" che altri utenti possono compilare.
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);
}
In seguito, l'incorporatore dell'elemento definisce questi valori a suo piacimento. Forse per abbinarlo al tema Comic Sans super cool della loro pagina:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
Grazie al modo in cui le variabili CSS ereditano, tutto va bene e questo funziona perfettamente. Il quadro completo è 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 i caratteri, i colori e le altezze delle righe, continuano a influire sugli elementi
nel DOM shadow. Tuttavia, per la massima flessibilità, Shadow DOM ci fornisce la proprietà resetStyleInheritance per controllare cosa succede al confine dello shadow.
In questo modo, puoi ricominciare da capo quando crei un nuovo componente.
resetStyleInheritance
false: predefinito. Le proprietà CSS ereditabili continuano a essere ereditate.true: reimposta le proprietà ereditabili suinitialal confine.
Di seguito è riportata una demo che mostra in che modo lo shadow tree è influenzato 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ù difficile, principalmente perché ha effetto solo sulle proprietà CSS ereditabili. In pratica, quando cerchi una proprietà da ereditare, al confine tra la pagina e lo ShadowRoot, non ereditare i valori dall'host, ma utilizza il valore initial (come da specifica CSS).
Se non sai quali proprietà vengono ereditate in CSS, consulta questo pratico elenco o attiva la casella di controllo "Mostra ereditate" nel riquadro Elemento.
Stile dei nodi distribuiti
I nodi distribuiti sono elementi che vengono visualizzati in un punto di inserimento (un elemento <content>). L'elemento <content> ti consente di selezionare i nodi dal Light DOM ed eseguirne il rendering in posizioni predefinite nello Shadow DOM. Non si trovano logicamente nello Shadow DOM, ma sono comunque elementi secondari dell'elemento host. I punti di inserimento sono solo un elemento di 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 inserimento. Anche in questo caso, i nodi distribuiti sono ancora logicamente nel DOM chiaro e non si spostano. Vengono semplicemente visualizzati altrove. Tuttavia, quando i nodi vengono distribuiti nello Shadow DOM, possono assumere stili aggiuntivi definiti all'interno dello shadow tree.
Pseudo-elemento ::content
I nodi distribuiti sono elementi secondari dell'elemento host, quindi come possiamo sceglierli come target all'interno dello Shadow DOM? La risposta è lo pseudo-elemento CSS ::content.
È un modo per scegliere come target i nodi Light DOM che passano attraverso un punto di inserimento. Ad esempio:
::content > h3 applica lo stile a tutti i tag h3 che passano attraverso un punto di inserimento.
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 inserimento
Quando crei un elemento ShadowRoot, hai la possibilità di reimpostare gli stili ereditati.
Anche i punti di inserimento <content> e <shadow> hanno questa opzione. Quando utilizzi questi elementi, imposta .resetStyleInheritance in JS o utilizza l'attributo booleano reset-style-inheritance sull'elemento stesso.
Per un ShadowRoot o i punti di inserimento
<shadow>:reset-style-inheritancesignifica che le proprietà CSS ereditabili sono impostate suinitialnell'host, prima che raggiungano i contenuti shadow. Questa posizione è nota come limite superiore.Per i punti di inserimento
<content>:reset-style-inheritanceindica che le proprietà CSS ereditabili sono impostate suinitialprima che gli elementi secondari dell'host vengano distribuiti nel punto di inserimento. Questa posizione è nota come limite inferiore.
Conclusione
In qualità di autori di elementi personalizzati, abbiamo a disposizione un'ampia gamma di opzioni per controllare l'aspetto dei nostri contenuti. Shadow DOM costituisce la base di questo nuovo mondo.
Shadow DOM ci offre un'incapsulazione dello stile con ambito e un modo per far entrare il mondo esterno nella misura che preferiamo. Definendo pseudo-elementi personalizzati o includendo segnaposto di variabili CSS, gli autori possono fornire a terze parti comodi hook di stile per personalizzare ulteriormente i loro contenuti. In sintesi, gli autori web hanno il pieno controllo su come vengono rappresentati i loro contenuti.