Introducción a Shadow DOM

Introducción

Web Components es un conjunto de estándares de vanguardia que:

  1. Permitir la compilación de widgets
  2. ... que se pueden volver a usar de manera confiable
  3. ...y cuáles no interrumpirán las páginas si la siguiente versión del componente cambia los detalles internos de implementación.

¿Significa que debes decidir cuándo usar HTML/JavaScript? cuándo usar componentes web? ¡No! HTML y JavaScript pueden hacer elementos visuales interactivos. Los widgets son elementos visuales interactivos. Integra es conveniente aprovechar tus habilidades en HTML y JavaScript cuando desarrollar un widget. Los estándares de componentes web están diseñados para ayudar lo haces.

Pero existe un problema fundamental que hace que los widgets se compilen a partir de HTML y JavaScript es difícil de usar: el árbol del DOM dentro de un widget no está encapsulada en el resto de la página. Esta falta de encapsulamiento significa que tu hoja de estilo del documento podría aplicarse accidentalmente a partes dentro del widget; tu JavaScript podría modificar partes dentro del widget; tus ID pueden superponerse con los ID dentro del widget; etcétera.

Los componentes web se componen de tres partes:

  1. Plantillas
  2. Shadow DOM
  3. Elementos personalizados

Shadow DOM soluciona el problema de encapsulamiento del árbol del DOM. El cuatro partes de los componentes web están diseñadas para funcionar juntas, pero también puede seleccionar las partes de los componentes web que desea utilizar. Esta este instructivo te muestra cómo usar Shadow DOM.

.

Hola, Mundo Sombrío

Con Shadow DOM, los elementos pueden obtener un nuevo tipo de nodo asociado con de ellos. Este nuevo tipo de nodo se denomina shadow root. Un elemento que tiene una shadow root asociada se denomina sombra host. El contenido de un host paralelo no se renderiza. el contenido de en su lugar, se renderiza la shadow root.

Por ejemplo, si tuvieras un lenguaje de marcado como el siguiente:

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

luego, en lugar de

<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>

cómo se ve tu página

<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>

Y eso no es todo, si JavaScript en la página se pregunta cuál es el botón textContent no recibirá “PRIVACYんappspotちの影の世界!", pero “¡Hola, mundo!”. porque el subárbol del DOM debajo de la shadow root, se encapsula.

Separar el contenido de la presentación

Ahora, veremos el uso de Shadow DOM para separar el contenido de presentación. Digamos que tenemos esta etiqueta de identificación:

<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>

Este es el lenguaje de marcado. Esto es lo que escribirías hoy. No usa 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>

Debido a que el árbol del DOM carece de encapsulamiento, toda la estructura del etiqueta personal está expuesta al documento. Si otros elementos en la página accidentalmente usamos los mismos nombres de clase para estilo o secuencia de comandos, la pasaría mal.

Podemos evitar pasar un mal momento.

Paso 1: Oculta los detalles de la presentación

Semánticamente, es probable que solo nos interese lo siguiente:

  • Es una etiqueta personal.
  • Se llama "Bob".

Primero, escribimos lenguaje de marcado que esté más cerca de la semántica real que deseamos:

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

Luego, pusimos todos los estilos y divs utilizados para la presentación en 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>

En este punto, “Bob” es lo único que está renderizado. Debido a que movimos los elementos del DOM de presentación un elemento <template>, no se renderizan, pero se puede acceder a ellas desde JavaScript. Lo hacemos ahora para propaga la shadow root:

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

Ahora que configuramos una shadow root, la etiqueta de identificación se renderiza de nuevo. Si haces clic con el botón derecho en la etiqueta de identificación e inspecciones el elemento, verás que es lenguaje de marcado semántico dulce:

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

Esto demuestra que, usando Shadow DOM, ocultamos el los detalles de la presentación de la etiqueta personal del documento. El los detalles de la presentación se encapsulan en el Shadow DOM.

Paso 2: Separa el contenido de la presentación

Ahora, nuestra etiqueta de identificación oculta los detalles de la presentación de la página, pero en realidad no separa la presentación del contenido porque, aunque el contenido (el nombre “Bob”) está en la página, el nombre que se representa es el que copiamos en shadow root. Si queremos cambiar en la etiqueta personal, tendríamos que hacerlo en dos lugares, y podrían se desincronizan.

Los elementos HTML son compositivos: puedes colocar un botón dentro de una tabla, por ejemplo. Lo que necesitamos aquí es la composición: la etiqueta debe ser composición del fondo rojo, el mensaje "Hi!" el texto y el contenido que está en la etiqueta de identificación.

Tú, el autor del componente, defines cómo funciona la composición con tus con un nuevo elemento llamado <content>. Esta crea un punto de inserción en la presentación del widget y el el punto de inserción selecciona cuidadosamente el contenido del host paralelo para presentar. en ese momento.

Si cambiamos el lenguaje de marcado en el Shadow DOM por el siguiente:

<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>

Cuando se renderiza la etiqueta de nombre, el contenido del host paralelo se se proyecta en el lugar en el que el elemento <content> .

Ahora la estructura del documento es más simple porque el nombre solo en un solo lugar: el documento. Si alguna vez necesitas actualizar tu página de usuario, solo tienes que escribir:

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

y eso es todo. La renderización de la etiqueta de identificación se actualiza automáticamente. el navegador, ya que proyectamos el contenido de la la etiqueta personal en su lugar con <content>.

<div id="ex2b">

Ahora logramos la separación del contenido y la presentación. El el contenido está en el documento. la presentación está en el Shadow DOM. El navegador los mantiene sincronizados automáticamente cuando llega el para renderizar algo.

Paso 3: Ganancias

Al separar el contenido y la presentación, podemos simplificar el código que manipula el contenido; en el ejemplo de la etiqueta de identificación, que código solo necesita lidiar con una estructura simple que contiene un <div> en lugar de varios.

Si cambiamos nuestra presentación, no necesitamos cambiar ninguno de los código.

Por ejemplo, supongamos que queremos localizar nuestra etiqueta personal. Sigue siendo un nombre etiqueta para que el contenido semántico del documento no cambie:

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

El código de configuración de la raíz secundaria se mantiene igual. Lo que se ingresa en el Cambios en shadow root:

<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>

Esta es una gran mejora con respecto a la situación actual en la web, porque el código de actualización de tu nombre puede depender de la estructura del componente simple y coherente. Tu nombre de actualización del código no necesita conocer la estructura que se usa para y procesamiento. Si consideramos lo que se renderiza, el nombre aparece segundo en inglés (después de "Hi! Mi nombre es”), pero primero en japonés (antes de “と申ます”). Esa distinción no tiene sentido semánticamente. desde el punto de vista de la actualización del nombre que se muestra para que el código de actualización de nombre no deba conocer esos detalles.

Crédito extra: Proyección avanzada

En el ejemplo anterior, el elemento <content> selecciona cuidadosamente todo el contenido del host paralelo. Mediante select, puedes controlar qué de un elemento de contenido. También puedes usar varios tipos de contenido o de terceros.

Por ejemplo, si tienes un documento que contiene lo siguiente:

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

y una shadow root que usa selectores CSS para seleccionar contenido específico:

<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>

El elemento <div class="email"> coincide con ambos los elementos <content select="div"> y <content select=".email"> ¿Cuántas veces envía un correo electrónico a Roberto? y en qué colores?

La respuesta es que la dirección de correo electrónico de Beto aparece una vez, y es amarilla.

Esto se debe a que, como saben las personas que hackean Shadow DOM, construir el árbol de lo que realmente se renderiza en pantalla es como una gran fiesta. El elemento de contenido es la invitación que contenido del documento en la renderización de Shadow DOM tras bambalinas de las partes interesadas. Estas invitaciones se entregan en orden. que recibe un de la invitación depende de a quién se dirija (es decir, el atributo select). Contenido una vez invitado, siempre acepta la invitación (¡¿quién no?) y la rechaza en la nube. Si se vuelve a enviar una invitación posterior a esa dirección, no haya nadie en casa ni vaya a tu fiesta.

En el ejemplo anterior, <div class="email"> coincide. el selector div y el .email pero como el elemento de contenido con el elemento div selector aparece antes en el documento, <div class="email"> va a la fiesta amarilla y nadie está disponible para venir a la fiesta azul. (Eso podría sé por qué es tan azul, aunque la tristeza ama la compañía, así que nunca lo sabrás).

Si se invita a algo a ningún grupo, no se recibe que se renderizan. Eso es lo que sucedió con el texto "Hello, world" en el primer ejemplo. Esto es útil cuando se desea lograr un renderización radicalmente diferente: escribe el modelo semántico en el que es lo que es accesible para las secuencias de comandos de la página, pero los con fines de renderización y conectarlo a una interfaz de procesamiento en Shadow DOM mediante JavaScript.

Por ejemplo, HTML tiene un buen selector de fecha. Si escribes <input type="date">, verás un calendario emergente. Pero ¿qué sucede si quieren permitir que el usuario elija un intervalo de fechas para su postre vacaciones en una isla con hamacas hechas de enredaderas rojas. Tú configura tu documento de esta manera:

<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>

pero crear Shadow DOM que usa una tabla para crear un calendario impecable que destaca el rango de fechas y así sucesivamente. Cuando el usuario hace clic en los días en el calendario, el componente actualiza el estado en la entradas startDate y endDate; Cuando el usuario envía el formulario, de esos elementos de entrada se envían.

¿Por qué incluí etiquetas en el documento si no van a estar se renderiza? Esto se debe a que, si un usuario ve el formulario desde un navegador que no admite Shadow DOM, el formulario se puede usar, pero no tan es bonita. El usuario ve algo como lo siguiente:

<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>

Apruebas Shadow DOM 101

Esos son los conceptos básicos de Shadow DOM: ¡pasaste Shadow DOM 101! Puedes hacer más cosas con Shadow DOM; por ejemplo, puedes usar varias sombras en un host paralelo o sombras anidadas para el encapsulamiento, o tu página usando vistas basadas en modelos (MDV) y Shadow DOM. Y Web Los componentes son más que solo Shadow DOM.

Esto se explica en publicaciones posteriores.