DOM 101 shadow

Dominic Cooney
Dominic Cooney

Introduzione

I componenti web sono un insieme di standard all'avanguardia che:

  1. Rendere possibile la creazione di widget
  2. ...che possono essere riutilizzate in modo affidabile
  3. ...e che non interromperanno le pagine se la versione successiva del componente modifica i dettagli di implementazione interni.

Significa che devi decidere quando utilizzare HTML/JavaScript e quando utilizzare i componenti web? No! HTML e JavaScript possono rendere elementi visivi interattivi. I widget sono elementi visivi interattivi. it ha senso sfruttare le tue competenze in HTML e JavaScript quando sviluppare un widget. Gli standard dei componenti web sono studiati per aiutare puoi farlo.

Ma c'è un problema fondamentale per cui i widget sono basati HTML e JavaScript difficili da usare: l'albero DOM all'interno di un widget incapsulati dal resto della pagina. L'assenza di incapsulamento significa che il foglio di stile del documento potrebbe applicarsi accidentalmente a parti all'interno del widget; il codice JavaScript potrebbe accidentalmente modificare parti all'interno del widget; gli ID potrebbero sovrapporsi a quelli all'interno del widget; e così via.

I componenti web sono composti da tre parti:

  1. Modelli
  2. DOM shadow
  3. Elementi personalizzati

Shadow DOM risolve il problema di incapsulamento dell'albero del DOM. La le quattro parti dei componenti web sono studiate per funzionare insieme, ma può anche selezionare le parti dei componenti web da utilizzare. Questo il tutorial mostra come utilizzare Shadow DOM.

Ciao, Shadow World

Con Shadow DOM, gli elementi possono ottenere un nuovo tipo di nodo associato che li rappresentano. Questo nuovo tipo di nodo è chiamato root shadow. Un elemento a cui è associata una radice ombra si chiama shadow . Il rendering dei contenuti di un host shadow non viene eseguito, i contenuti di viene invece eseguito il rendering della radice ombra.

Ad esempio, se avessi un markup simile al seguente:

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

invece di

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

la tua pagina ha questo aspetto:

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

Inoltre, se JavaScript sulla pagina chiede quale sia il contenuto textContent è, non riceverà "ちんちちち、影の世界!"", ma "Ciao, mondo!" perché il sottoalbero del DOM sotto la radice ombra è incapsulata.

Separazione dei contenuti dalla presentazione

Ora vedremo come usare Shadow DOM per separare i contenuti una presentazione. Supponiamo di avere questo tag:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

Ecco il markup. Questo è ciò che scriveresti oggi. Non utilizza Shadow DOM:

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

Poiché nell'albero DOM manca l'incapsulamento, l'intera struttura del sia esposto al documento. Se ci sono altri elementi nella pagina usare accidentalmente gli stessi nomi di classi per lo stile o lo scripting, non andrà bene.

Possiamo evitare di correre in difficoltà.

Passaggio 1: nascondi i dettagli della presentazione

Dal punto di vista semantico probabilmente ci interessa solo:

  • Si tratta di un tag.
  • Il nome è "Bob".

Per prima cosa, scriviamo il markup più vicino alla vera semantica che vogliamo:

<div id="nameTag">Bob</div>

Quindi inseriamo tutti gli stili e i div utilizzati per la presentazione un elemento <template>:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

A questo punto, viene eseguito il rendering dell'unica cosa, "Bob". Poiché ha spostato gli elementi DOM di presentazione un elemento <template>, non vengono sottoposti a rendering, a cui è possibile accedere da JavaScript. Ora lo facciamo per popola la radice shadow:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

Ora che abbiamo configurato una radice shadow, il tag nome viene visualizzato di nuovo. Se fai clic con il tasto destro del mouse sul tag e ispeziona che è un semplice markup semantico:

<div id="nameTag">Bob</div>

Questo dimostra che, utilizzando Shadow DOM, abbiamo nascosto dettagli di presentazione dell'etichetta del nome dal documento. La i dettagli della presentazione sono incapsulati nel DOM Shadow.

Passaggio 2: separa i contenuti dalla presentazione

Il nostro tag ora nasconde i dettagli della presentazione nella pagina, ma non separa effettivamente la presentazione dai contenuti, perché sebbene i contenuti (il nome "Bob") sono nella pagina, il nome visualizzato copiata nella radice ombra. Se vogliamo modificare nome sul tag, dobbiamo farlo in due punti e loro potrebbero non sincronizzati.

Gli elementi HTML sono composizioni: puoi inserire un pulsante all'interno di una tabella, ad esempio. La composizione è ciò di cui abbiamo bisogno qui: il tag deve essere una sullo sfondo rosso, la frase "Ciao!" il testo e i contenuti che si trova sul tag.

In qualità di autore del componente, stabilisci il funzionamento della composizione con il tuo widget utilizzando un nuovo elemento chiamato <content>. Questo crea un punto di inserimento nella presentazione del widget e punto di inserimento che seleziona i contenuti dall'host shadow per presentare a quel punto.

Se modifichiamo il markup nel DOM shadow in questo modo:

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

Quando viene eseguito il rendering del tag, i contenuti dell'host shadow vengono proiettato nel punto in cui l'elemento <content> .

Ora la struttura del documento è più semplice perché il nome è in un unico posto: il documento. Se la tua pagina deve aggiornare il nome utente, basta scrivere:

document.querySelector('#nameTag').textContent = 'Shellie';

Ecco fatto. Il rendering del tag viene aggiornato automaticamente dal browser, perché stiamo proiettando i contenuti del il tag in <content>.

<div id="ex2b">

Ora che abbiamo separato i contenuti e la presentazione, La i contenuti siano presenti nel documento; la presentazione si trova nel DOM shadow. Vengono automaticamente sincronizzati dal browser al momento opportuno per eseguire il rendering di qualcosa.

Passaggio 3. Profitto

Separando contenuti e presentazione, possiamo semplificare che manipola i contenuti, nell'esempio del tag nome, il codice deve essere gestito con una semplice struttura contenente un <div> invece di più.

Ora, se modifichiamo la presentazione, non è necessario cambiare il codice sorgente.

Ad esempio, supponiamo di voler localizzare il nostro tag. È ancora un nome , quindi il contenuto semantico del documento non cambia:

<div id="nameTag">Bob</div>

Il codice di configurazione principale shadow rimane lo stesso. Proprio quello che viene inserito modifiche alla directory principale shadow:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

Si tratta di un grande miglioramento rispetto alla situazione sul web oggi, perché il codice di aggiornamento del nome può dipendere dalla struttura del componente semplice e coerente. Il tuo nome di aggiornamento non deve conoscere la struttura utilizzata per il rendering delle immagini. Se consideriamo ciò che viene visualizzato, il nome viene visualizzato secondo in inglese (dopo "Ciao! mi chiamo"), ma prima in giapponese (prima di "と申ます"). Questa distinzione è semanticamente priva di significato dal punto di vista dell'aggiornamento del nome visualizzato, in modo che il codice di aggiornamento del nome non debba conoscere questi dettagli.

Credito aggiuntivo: proiezione avanzata

Nell'esempio precedente, l'elemento <content> seleziona tutti i contenuti dall'host shadow. Utilizzando i comandi attributo select, puoi controllare cosa viene proiettato un elemento di contenuti. Puoi anche usare più contenuti elementi.

Ad esempio, se un tuo documento contiene:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

e una radice shadow che utilizza i selettori CSS per selezionare contenuti specifici:

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

L'elemento <div class="email"> corrisponde a entrambi gli elementi <content select="div"> e <content select=".email">. Quante volte invia l'email di Mario e in quali colori?

La risposta è che l'indirizzo email di Roberto viene visualizzato una sola volta ed è giallo.

Il motivo è che, come sanno già le persone che hackerano Shadow DOM, per creare l'albero di ciò che viene effettivamente visualizzato sullo schermo è grande festa. L'elemento Content è l'invito che consente dal documento al rendering backstage Shadow DOM . Questi inviti vengono consegnati nell'ordine indicato: chi riceve l'invito dipende dal destinatario dell'invito (ovvero, l'attributo select. Contenuti, una volta invitato, accetta sempre l'invito (chi non lo farebbe?) e disattiva l'invito . Se un invito successivo viene inviato di nuovo a quell'indirizzo, non c'è nessuno a casa e non arriva alla tua festa.

Nell'esempio precedente, <div class="email"> corrisponde a sia il selettore div che .email ma poiché l'elemento di contenuti con div precedente nel documento, <div class="email"> va alla festa gialla e nessuno è disponibile per venire alla festa blu. (Ciò potrebbe sia perché è così blu, anche se la miseria ama la compagnia, quindi non saprei mai.

Se un utente viene invitato a nessun gruppo, non riceve il rendering dei contenuti. Ecco cosa è successo al testo "Hello World" nella il primo esempio. Ciò è utile per ottenere rendering radicalmente diverso: scrivi il modello semantico documento accessibile agli script nella pagina, ma nascondi ai fini del rendering e collegarla a un ambiente di rendering del modello in Shadow DOM tramite JavaScript.

Ad esempio, HTML dispone di un bel selettore di date. Se scrivi <input type="date">, verrà visualizzato un bellissimo calendario popup. Ma cosa succede Vuoi consentire all'utente di scegliere un intervallo di date per il suo dessert un'isola in vacanza (con amache fatte di Red Vines). Tu configura il documento nel seguente modo:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

ma crea un DOM shadow che utilizza una tabella per creare un calendario efficiente che evidenzia l'intervallo di date e così via. Quando l'utente fa clic su i giorni del calendario, il componente aggiorna lo stato nel gli input startDate e endDate; quando l'utente invia il modulo, vengono inviati i valori di questi elementi di input.

Perché ho incluso nel documento delle etichette se non saranno eseguire il rendering? Il motivo è che se un utente visualizza il modulo con un browser che non supporta Shadow DOM, il modulo è comunque utilizzabile, ma non come molto bello. L'utente vede qualcosa del tipo:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

Supera il DOM Ombra 101

Queste sono le basi del DOM Ombra: superi DOM Ombra 101! Puoi Ottieni il massimo da Shadow DOM; ad esempio, puoi utilizzare più shadow un host shadow o ombre nidificate per l'incapsulamento, della pagina utilizzando le viste basate su modelli (MDV) e Shadow DOM. E web I componenti sono molto di più di un semplice Shadow DOM.

Questo aspetto verrà spiegato nei post successivi.