DOM 201

CSS e stili

Questo articolo illustra altre fantastiche cose che puoi fare con Shadow DOM. Si basa sui concetti discussi in Shadow DOM 101. Se cerchi una panoramica, consulta questo articolo.

Introduzione

Ammettiamolo. Il markup non stilato 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 gratuitamente. In un altro modo:

<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 ci sono altri h3, ma l'unico che corrisponde al selettore h3, e di conseguenza in rosso, è quello in ShadowRoot. Anche in questo caso, gli stili basati sugli ambiti sono predefiniti.
  • Altre regole di stile definite in questa pagina che hanno come target h3 non vengono applicate ai miei contenuti. Questo perché i selettori non attraversano il confine dell'ombra.

Morale della storia? Abbiamo l'incapsulamento dello stile del mondo esterno. Grazie, DOM Ombra!

Stilizzazione dell'elemento host

:host ti consente di selezionare e definire lo stile dell'elemento che ospita un albero delle ombre:

<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 sostituire il tuo 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>) ti 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) {
    ...
}

Reazione agli stati degli utenti

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 impostarne lo stile in modo univoco 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>. */
}

Stile dell'interno del DOM Shadow dall'esterno

Lo pseudo-elemento ::shadow e il combinatore /deep/ sono come avere una Spada Vorpal dell'autorità CSS. Consentono di attraversare il confine del DOM ombra per applicare stili agli elementi all'interno degli alberi ombra.

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 una radice ombra, puoi scrivere #host::shadow span {} per applicare uno stile a tutte le sezioni all'interno dell'albero ombra.

<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 h2 intestazioni. 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 delle ombre e attraversa un numero qualsiasi di alberi delle ombre. In parole povere, /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 Shadow DOM. Gli esempi principali includono la nidificazione di una serie di elementi personalizzati (ciascuno che ospita 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 ombra per l'attraversamento DOM, i combinatori aprono gli alberi ombra per l'attraversamento del selettore. 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 stilare. Molte persone si arrendono e creano il proprio sito. Tuttavia, con ::shadow e /deep/, è possibile definire uno stile per 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 aspetto positivo. In alcuni casi potresti voler fare dei buchi nello scudo della tua ombra e creare ganci per farli disegnare ad altri.

Utilizzo di ::shadow e /deep/

/deep/ è molto potente. Offre agli autori dei componenti un modo per definire singoli elementi come stilizzabili o una serie di elementi come tematici.

Esempio: applica uno stile a tutti gli elementi con la classe .library-theme, ignorando tutti gli alberi di 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;
}

In modo simile al modo in cui i browser forniscono hook di stile in alcuni elementi interni, gli autori di contenuti Shadow DOM possono designare determinati elementi come stilizzabili dagli esterni. Questo viene fatto tramite 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 utenti esterni 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, si tratta di creare "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. Magari per adattare il fantastico tema Comic Sans della 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 altezza delle righe, continuano a influire sugli elementi in 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: impostazione predefinita. Le proprietà CSS ereditabili continuano a ereditare.
  • true: reimposta le proprietà ereditabili su initial al confine.

Di seguito è riportata una demo che mostra in che modo l'albero delle ombre è 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>
Proprietà ereditate di DevTools

Comprendere .resetStyleInheritance è un po' più complicato, principalmente perché ha effetto solo sulle proprietà CSS ereditabili. Dice: quando cerchi una proprietà da ereditare, al confine tra la pagina e ShadowRoot non ereditare valori dall'host, ma utilizza il valore initial (secondo la specifica CSS).

Se hai dubbi sulle proprietà ereditate in CSS, consulta questo pratico elenco o attiva/disattiva la casella di controllo "Mostra ereditate" nel riquadro Elemento.

Stilizzazione 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 ad essere applicate agli elementi, anche quando vengono visualizzati in un punto di inserimento. Ancora una volta, i nodi distribuiti sono ancora logicamente nel DOM della luce e non si spostano. ma vengono 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 inserimento. 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 inserimento

Quando crei un ShadowRoot, hai la possibilità di reimpostare gli stili ereditati. Anche <content> e <shadow> punti di inserzione hanno 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 su initial 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 su initial prima che gli elementi secondari dell'elemento host vengano distribuiti nel punto di inserzione. Questa località è nota come confine inferiore.

Conclusione

In qualità di autori di elementi personalizzati, abbiamo moltissime opzioni per controllare l'aspetto e il design dei nostri contenuti. Il DOM ombra costituisce la base di questo nuovo mondo.

Shadow DOM ci offre un'incapsulamento dello stile con ambito e un mezzo per far entrare il mondo esterno che vogliamo. Se definiscono pseudoelementi personalizzati o includono segnaposto per le 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.