DOM 201

CSS e stili

Questo articolo illustra in modo più approfondito le cose straordinarie che puoi fare con Shadow DOM. Si basa sui concetti discussi in Shadow DOM 101. Se cerchi un'introduzione, leggi questo articolo.

Introduzione

Ammettiamolo. Non c'è niente di sexy nel markup senza stile. Per nostra fortuna, le persone brillanti di Web Componenti lo hanno previsto e non ci hanno lasciato in sospeso. Il modulo di ambito CSS definisce molte opzioni per applicare uno stile ai contenuti in un albero di ombreggiatura.

Incapsulamento dello stile

Una delle caratteristiche principali di Shadow DOM è il confine shadow. Ha molte proprietà utili, ma una delle migliori è che offre un incapsulamento di stile senza costi. In altri termini:

<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 valori h3, ma l'unico che corrisponde al selettore h3, e quindi con lo stile rosso, è quello in ShadowRoot. Anche in questo caso, gli stili con ambito sono predefiniti.
  • Le altre regole per gli stili definite in questa pagina e che hanno come target h3 non rientrano nei miei contenuti. Questo perché i selettori non superano il limite dell'ombra.

Morale della storia? Abbiamo un incapsulamento in stile proveniente dal mondo esterno. Grazie Shadow DOM!

Applicare uno stile all'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 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 modificare lo stile dall'esterno. :host funziona anche solo nel contesto di ShadowRoot, quindi non è possibile 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: trova corrispondenze 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 di :host è quando crei un elemento personalizzato e vuoi reagire a diversi stati dell'utente (:passaggio del mouse, :focus, :attivo 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>

Temi di un elemento

La pseudo classe :host-context(<selector>) corrisponde all'elemento host se l'elemento stesso o uno dei suoi predecessori corrisponde a <selector>.

:host-context() è comunemente utilizzato per tematizzare un elemento in base all'ambiente circostante. Ad esempio, molte persone applicano un tema applicando un corso a <html> o <body>:

<body class="different">
  <x-foo></x-foo>
</body>

Puoi :host-context(.different) per definire <x-foo> quando è un discendente di un elemento con la classe .different:

:host-context(.different) {
  color: red;
}

Ciò ti dà la possibilità di incapsulare le regole di stile nel DOM shadow di un elemento, in modo da applicarne uno stile univoco in base al suo contesto.

Supporta più tipi di host all'interno di una radice shadow

:host può essere utilizzato anche se stai creando una libreria di temi e vuoi supportare lo stile di molti tipi di elementi host all'interno dello stesso DOM Shadow.

: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 del DOM shadow dall'esterno

Lo pseudo-elemento ::shadow e il combinatore /deep/ sono come avere una spada Vorpal dell'autorità CSS. Consentono di forare i confini di Shadow DOM per applicare uno stile agli elementi all'interno degli alberi delle ombre.

Lo pseudo-elemento ::shadow

Se un elemento ha almeno un albero shadow, lo pseudo-elemento ::shadow corrisponde alla radice shadow stessa. Consente di scrivere selettori che definiscono i nodi interni al dominio shadow di un elemento.

Ad esempio, se un elemento ospita una radice shadow, puoi scrivere #host::shadow span {} per applicare uno stile a tutte le sezioni all'interno del relativo albero shadow.

<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 DOM shadow. Ogni riquadro ospita il proprio albero delle ombre contenente le intestazioni h2. Per applicare uno stile a queste intestazioni nella pagina principale, potresti scrivere:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

Il combinatore /deep/

Il combinatore di /deep/ è simile a ::shadow, ma più potente. Ignora completamente tutti i confini con le ombre e si estende fino a raggiungere un numero qualsiasi di alberi frondosi. In breve, /deep/ consente di visualizzare in dettaglio le viscere di 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 sono la nidificazione di un gruppo di elementi personalizzati (ognuno che ospita il proprio albero delle ombre) o la creazione di un elemento che eredità 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 di ombreggiatura:

body /deep/ .library-theme {
    ...
}

Utilizzo di querySelector()

Proprio come .shadowRoot apre gli alberi delle ombre per l'attraversamento del DOM, i combinatori aprono gli alberi delle ombre per l'attraversamento del selettore. Anziché scrivere una catena nidificata di follia, puoi scrivere una singola affermazione:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

Applicare uno stile agli elementi nativi

Lo stile dei controlli HTML nativi è una sfida. Molte persone si arrendono e si impegnano a fare la propria. Tuttavia, con ::shadow e /deep/, puoi applicare uno stile a qualsiasi elemento nella piattaforma web che utilizza Shadow DOM. Esempi eccezionali sono i tipi <input> e <video>:

video /deep/ input[type="range"] {
  background: hotpink;
}

Creazione di hook di stile

La personalizzazione va bene. In alcuni casi potresti voler fare dei buchi nello scudo della tua Ombra e creare ganci per farli scegliere ad altri.

Utilizzo di ::shadow e /deep/

C'è molta energia dietro /deep/. Offre agli autori dei componenti un modo per classificare singoli elementi come stilizzabili o numerosi elementi come temibili.

Esempio: applica uno stile a tutti gli elementi con classe .library-theme, ignorando tutti gli alberi delle ombre:

body /deep/ .library-theme {
    ...
}

Utilizzo di pseudo elementi personalizzati

Sia WebKit che Firefox definiscono pseudoelementi per lo stile delle parti interne degli elementi nativi del browser. Un buon esempio è input[type=range]. Puoi modificare lo stile del cursore del 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;
}

In modo simile a come i browser forniscono hook di stile in alcuni elementi interni, gli autori dei contenuti Shadow DOM possono designare alcuni elementi come stilizzabili da utenti esterni. Ciò avviene tramite pseudo elementi personalizzati.

Puoi designare un elemento come pseudo elemento personalizzato utilizzando l'attributo pseudo. Il suo valore, o nome, deve essere preceduto da "x-". In questo modo viene creata un'associazione con l'elemento nell'albero d'ombra e gli utenti esterni riceveranno una corsia designata per oltrepassare il limite in ombra.

Ecco un esempio di creazione di un widget del dispositivo di scorrimento personalizzato e di come consentire a qualcuno di applicare uno stile al cursore blu del 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>

Utilizzare le variabili CSS

Un modo efficace per creare hook dei temi sarà tramite le variabili CSS. Essenzialmente, creare "segnaposto di stile" che altri utenti possono compilare.

Immagina un autore di elementi personalizzati che contrassegna i segnaposto variabili nel DOM shadow. Uno per definire 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);
}

Quindi, l'incorporamento dell'elemento definisce tali valori a proprio piacimento. Magari per abbinare il tema fantastico del Comic Sans della loro pagina:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

A causa del modo in cui le variabili CSS ereditano, tutto è impeccabile e funziona alla grande. Il quadro completo ha un aspetto simile a questo:

<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 altezze delle righe, continuano a influire sugli elementi nel DOM di Shadow. Tuttavia, per la massima flessibilità, Shadow DOM ci fornisce la proprietà resetStyleInheritance per controllare cosa succede in corrispondenza del confine dell'ombra. Puoi considerarla come un modo per ricominciare da zero quando crei un nuovo componente.

resetStyleInheritance

  • false: impostazione predefinita. Le proprietà CSS ereditabili continuano a ereditare.
  • true: reimposta le proprietà ereditabili su initial in corrispondenza del confine.

Di seguito è riportata una demo che mostra in che modo la modifica di resetStyleInheritance influisce sull'albero delle ombre:

<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à DevTools ereditate

Comprendere .resetStyleInheritance è un po' più complicato, principalmente perché ha un impatto solo sulle proprietà CSS che sono ereditabili. Dice: se cerchi una proprietà da ereditare, al confine tra la pagina e ShadowRoot, non ereditare valori dall'host ma usa il valore initial (in base alle specifiche CSS).

Se hai dubbi in merito alle proprietà 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> consente di selezionare i nodi del DOM Light e di eseguirne il rendering in posizioni predefinite nel DOM shadow. Non si trovano logicamente nel DOM Shadow; sono comunque figli dell'elemento host. I punti di inserzione sono solo un aspetto del rendering.

I nodi distribuiti conservano 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 dominio leggero e non si muovono. Vengono visualizzati altrove. Tuttavia, quando i nodi vengono distribuiti nel DOM shadow, possono assumere stili aggiuntivi definiti all'interno dell'albero shadow.

Pseudo elemento ::content

I nodi distribuiti sono elementi secondari dell'elemento host, quindi come possiamo sceglierli come target all'interno del DOM shadow? La risposta è lo pseudo elemento CSS ::content. È un modo per scegliere come target i nodi DOM Light che passano attraverso un punto di inserimento. Ad esempio:

::content > h3 applica uno stile 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 uno ShadowRoot, hai la possibilità di reimpostare gli stili ereditati. Anche <content> e <shadow> punti di inserzione dispongono di questa opzione. Quando utilizzi questi elementi, imposta .resetStyleInheritance in JS o utilizza l'attributo booleano reset-style-inheritance sull'elemento stesso.

  • Per un punto di inserzione ShadowRoot o <shadow>: reset-style-inheritance significa che le proprietà CSS ereditabili sono impostate su initial nell'host, prima che raggiungano i tuoi contenuti shadow. Questa località è nota come confine superiore.

  • Per <content> punti di inserzione: reset-style-inheritance significa che le proprietà CSS ereditabili vengono impostate su initial prima che gli elementi secondari dell'host vengano distribuiti nel punto di inserzione. Questa località è nota come confine inferiore.

Conclusione

In qualità di autori di elementi personalizzati, disponiamo di numerose opzioni per controllare l'aspetto e il design dei nostri contenuti. Shadow DOM è la base di questo nuovo mondo coraggioso.

Lo shadow DOM ci offre un incapsulamento di stile basato su ambito e un mezzo per far entrare quanto meno (o meno) del mondo esterno. Definindo pseudo elementi personalizzati o includendo i segnaposto delle variabili CSS, gli autori possono fornire a terze parti pratici hook per personalizzare ulteriormente i propri contenuti. Tutti gli autori di contenuti web hanno il pieno controllo di come vengono rappresentati i loro contenuti.