Trabajando con Shadow Dom – Magazine Smash
Muy común Componentes web Directamente En comparación con los componentes de cuadro. Pero la mayoría de los ejemplos en realidad están dirigidos a elementos personalizados, que es parte de la imagen del componente web. Es fácil olvidar que los componentes web son en realidad un conjunto separado de API de plataforma web que puede ser utilizada por usted mismo:
En otras palabras, puedes crear un Elementos personalizados No utilizado Cúpula de la sombra o Plantillas HTMLpero combinar estas características puede mejorar la estabilidad, la repetibilidad, la mantenimiento y la seguridad. Son parte del mismo conjunto de características que se pueden usar solos o juntos.
Habiendo dicho eso, quiero prestar atención especial Cúpula de la sombra Y la ubicación que se adapta a esta foto. Trabajar con Shadow DOM nos permite definir límites claros entre partes de una aplicación web – Paquete HTML y CSS relacionado DocumentFragment Aislar componentes, prevenir conflictos y mantener una separación limpia de las preocupaciones.
La forma en que aprovecha esta encapsulación implica compensaciones y múltiples enfoques. En este artículo, profundizaremos en estos matices, y en los artículos posteriores profundizaremos en cómo usar los estilos de encapsulación de manera efectiva.
Por qué hay una sombra
La mayoría de las aplicaciones web modernas están construidas a partir de una variedad de bibliotecas y componentes de varios proveedores. Usando DOM tradicional (o «ligero»), estilos y scripts se filtran o chocan entre sí. Si está utilizando un marco, puede creer que todo se ha escrito para funcionar sin problemas, pero aún tiene que funcionar para asegurarse de que todos los elementos tengan ID únicas y que las reglas de CSS estén lo más especializadas posible. Esto puede conducir a un código detallado excesivo, aumentar el tiempo de carga de la aplicación y reducir la mantenimiento.
<!-- div soup -->
<div id="my-custom-app-framework-landingpage-header" class="my-custom-app-framework-foo">
<div><div><div><div><div><div>etc...</div></div></div></div></div></div>
</div>
Shadow Dom se introdujo para resolver estos problemas al proporcionar una forma de aislar cada componente. este <video> y <details> Los elementos son buenos ejemplos de elementos HTML nativos que usan SHADOW DOM internamente de forma predeterminada para evitar la interferencia de los estilos o scripts globales. El uso de esta potencia oculta que impulsa los componentes del navegador nativo es una forma de configurar realmente diferentes componentes web de las contrapartes de Framework.

<details> La sombra dom de los elementos en Devtools. (Vista previa grande)Elementos que pueden acomodar las raíces de la sombra
La mayoría de las veces, verá raíces de la sombra asociadas con elementos personalizados. Sin embargo, también pueden estar con cualquier HTMLUnknownElementy Muchos elementos estándar Apoyarlos, incluido:
<aside><blockquote><body><div><footer><h1>llegar<h6><header><main><nav><p><section><span>
Cada elemento solo puede tener una raíz de sombra. Algunos elementos, incluido <input> y <select>ya tiene una raíz de sombra incorporada a la que no se puede acceder a través de scripts. Puede consultarlos habilitando herramientas de desarrollador Mostrar el agente de usuarios Shadow Dom Configuración, de forma predeterminada, está «desactivado».


Crear raíz de sombra
Antes de que pueda aprovechar los beneficios de Shadow Dom, debe construir un Raíz de la sombra En el elemento. Esto se puede instanciar en situaciones urgentes o declaradas.
Instanciación de integridad
Para crear raíces de sombra usando JavaScript, use attachShadow({ mode }) En el elemento. este mode Poder open (Permitido pasar element.shadowRoot) o closed (Ocultar la raíz de la sombra en un script externo).
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = '<p>Hello from the Shadow DOM!</p>';
document.body.appendChild(host);
En este ejemplo, hemos establecido open Raíz de la sombra. Esto significa que se puede acceder al contenido de ese elemento desde el exterior y podemos consultarlo como cualquier otro nodo DOM:
host.shadowRoot.querySelector('p'); // selects the paragraph element
Si queremos evitar que los scripts externos accedan completamente a nuestra estructura interna, podemos establecer el modo en closed en cambio. Esto lleva al elemento shadowRoot Atributos devueltos null. Todavía podemos obtener de el nuestro shadow Referencias en el alcance que creamos.
shadow.querySelector('p');
Esta es una característica de seguridad crucial. y closed Shadow Root, podemos estar convencidos de que los actores maliciosos no pueden extraer datos de usuarios privados de nuestros componentes. Por ejemplo, considere un widget que muestra información bancaria. Tal vez contiene la cuenta del usuario. y open Shadow Root, cualquier script en la página puede perforar en nuestros componentes y analizar su contenido. existir closed Modo, solo el usuario puede realizar tales operaciones copiando o verificando manualmente elementos.
Sugiero uno Un método cerrado Al trabajar con Shadow Dom. Hábito closed Patrones A menos que esté depurando, las inevitables limitaciones del mundo real solo se pueden resolver si es absolutamente necesario. Si sigue este método, encontrará open En realidad, se requieren pocos patrones y muy separados.
Instanciación declarativa
No tenemos que usar JavaScript para aprovechar a Shadow Dom. El registro de la raíz de la sombra se puede hacer declarativamente. nido <template> y shadowrootmode Cualquier atributo dentro de un elemento compatible hará que el navegador actualice automáticamente el elemento con una raíz de sombra. Conectar la raíz de la sombra de esta manera se puede hacer deshabilitando JavaScript.
<my-widget>
<template shadowrootmode="closed">
<p> Declarative Shadow DOM content </p>
</template>
</my-widget>
De nuevo, esto puede ser open o closed. Considere el significado de seguridad antes de usar open Modo, pero tenga en cuenta que no puede acceder closed El contenido del patrón a través de cualquier script a menos que este método esté con Registrado Elementos personalizados, en cuyo caso puede usar ElementInternals Acceso a la raíz de sombra adjunta automáticamente:
class MyWidget extends HTMLElement {
#internals;
#shadowRoot;
constructor() {
super();
this.#internals = this.attachInternals();
this.#shadowRoot = this.#internals.shadowRoot;
}
connectedCallback() {
const p = this.#shadowRoot.querySelector('p')
console.log(p.textContent); // this works
}
};
customElements.define('my-widget', MyWidget);
export { MyWidget };
Configuración DOM de sombra
Además de las otras tres opciones modelo Podemos pasar Element.attachShadow().
Opción 1: clonable:true
Hasta hace poco, si el elemento estándar tiene una raíz de sombra adjunta e intenta usar Node.cloneNode(true) o document.importNode(node,true)solo obtendrá una copia superficial del elemento host sin contenido de la raíz de la sombra. El ejemplo que solo miramos realmente devuelve uno vacío <div>. Esta de ninguna manera es una cuestión de elementos personalizados que establecen sus propias raíces de la sombra internamente.
Pero para el Dom Dom Dom, esto significa que cada elemento necesita su propia plantilla y no se puede reutilizar. Con esta nueva característica, podemos clonar selectivamente cuando sea necesario:
<div id="original">
<template shadowrootmode="closed" shadowrootclonable>
<p> This is a test </p>
</template>
</div>
<script>
const original = document.getElementById('original');
const copy = original.cloneNode(true); copy.id = 'copy';
document.body.append(copy); // includes the shadow root content
</script>
Opción 2: serializable:true
Habilite esta opción para permitirle guardar una representación de cadena de contenido en la raíz de la sombra de un elemento. Llamar Element.getHTML() Elementos en el host devolverá una copia de plantilla del estado actual de Shadow Dom, incluidas todas las instancias anidadas shadowrootserializable. Esto se puede usar para inyectar una copia de la raíz de la sombra en otro host, o almacenarla en caché para su uso posterior.
En Chrome, esto es en realidad Trabajando a través de la raíz de la sombra cerradaasí que tenga en cuenta los datos de los usuarios con fugas accidentales utilizando esta función. Una opción más segura es usar closed Envoltura para proteger el contenido interno de las influencias externas mientras mantiene las cosas open interno:
<wrapper-element></wrapper-element>
<script>
class WrapperElement extends HTMLElement {
#shadow;
constructor() {
super();
this.#shadow = this.attachShadow({ mode:'closed' });
this.#shadow.setHTMLUnsafe(`
<nested-element>
<template shadowrootmode="open" shadowrootserializable>
<div id="test">
<template shadowrootmode="open" shadowrootserializable>
<p> Deep Shadow DOM Content </p>
</template>
</div>
</template>
</nested-element>
`);
this.cloneContent();
}
cloneContent() {
const nested = this.#shadow.querySelector('nested-element');
const snapshot = nested.getHTML({ serializableShadowRoots: true });
const temp = document.createElement('div');
temp.setHTMLUnsafe(`<another-element>${snapshot}</another-element>`);
const copy = temp.querySelector('another-element');
copy.shadowRoot.querySelector('#test').shadowRoot.querySelector('p').textContent="Changed Content!";
this.#shadow.append(copy);
}
}
customElements.define('wrapper-element', WrapperElement);
const wrapper = document.querySelector('wrapper-element');
const test = wrapper.getHTML({ serializableShadowRoots: true });
console.log(test); // empty string due to closed shadow root
</script>
Aviso setHTMLUnsafe(). Eso es porque el contenido contiene <template> elemento. Este método debe llamarse durante la inyección Confiable Contenido de esta naturaleza. Insertar plantillas con plantillas innerHTML La inicialización automática no se activa en la raíz de la sombra.
Opción 3: delegatesFocus:true
Esencialmente, esta opción hace que nuestro elemento de host sea como un <label> Utilizado para contenido interno. Cuando esté habilitado, haga clic en cualquier lugar del host o llame .focus() En la parte superior, moverá el cursor al primer elemento de enfoque en la raíz de la sombra. Esto también se aplicará :focus El pseudo nivel es host, que es especialmente útil al crear componentes diseñados para participar en tablas.
<custom-input>
<template shadowrootmode="closed" shadowrootdelegatesfocus>
<fieldset>
<legend> Custom Input </legend>
<p> Click anywhere on this element to focus the input </p>
<input type="text" placeholder="Enter some text...">
</fieldset>
</template>
</custom-input>
Este ejemplo solo prueba la autorización de enfoque. Uno de los números impares de encapsulación es que el envío de la tabla no está conectado automáticamente. Esto significa que, de forma predeterminada, el valor ingresado no se enviará en el formulario de confirmación. La verificación y el estado formales tampoco se transmiten desde la sombra DOM. Hay un problema de conexión similar con la accesibilidad, y los límites de la raíz de la sombra pueden interferir con ARIA. Estas son consideraciones específicas para las tablas que podemos resolver ElementInternalsEste es el tema de otra publicación y hay razones para cuestionar si puede confiar en el formulario DOM óptico.
Contenido insertado
Hasta ahora, solo hemos examinado componentes completamente encapsulados. Una función de DOM de sombra clave es usar Tragaperras Inyectar contenido selectivamente en la estructura interna del componente. Cada raíz de la sombra puede tener uno por defecto (Sin nombre) <slot>; Todos los demás deben nombre. Las ranuras con nombre nos permiten proporcionar contenido para llenar partes específicas del componente, así como el contenido posterior para llenar las ranuras que el usuario omite:
<my-widget>
<template shadowrootmode="closed">
<h2><slot name="title"><span>Fallback Title</span></slot></h2>
<slot name="description"><p>A placeholder description.</p></slot>
<ol><slot></slot></ol>
</template>
<span slot="title"> A Slotted Title</span>
<p slot="description">An example of using slots to fill parts of a component.</p>
<li>Foo</li>
<li>Bar</li>
<li>Baz</li>
</my-widget>
La ranura predeterminada también admite contenido respaldo, pero cualquier nodo de texto errante los llenará. Como resultado, esto solo funciona si bloquea todos los espacios en la etiqueta del elemento host:
<my-widget><template shadowrootmode="closed">
<slot><span>Fallback Content</span></slot>
</template></my-widget>
Emisión del elemento de la ranura slotchange Cuando sus eventos assignedNodes() Agregar o eliminar. Estos eventos no contienen referencias a ranuras o nodos, por lo que debe pasarlos al controlador activo:
class SlottedWidget extends HTMLElement {
#internals;
#shadow;
constructor() {
super();
this.#internals = this.attachInternals();
this.#shadow = this.#internals.shadowRoot;
this.configureSlots();
}
configureSlots() {
const slots = this.#shadow.querySelectorAll('slot');
console.log({ slots });
slots.forEach(slot => {
slot.addEventListener('slotchange', () => {
console.log({
changedSlot: slot.name || 'default',
assignedNodes: slot.assignedNodes()
});
});
});
}
}
customElements.define('slotted-widget', SlottedWidget);
Se pueden asignar múltiples elementos a una ranura o slot Propiedades o a través de scripts:
const widget = document.querySelector('slotted-widget');
const added = document.createElement('p');
added.textContent="A secondary paragraph added using a named slot.";
added.slot="description";
widget.append(added);
Tenga en cuenta que los párrafos en este ejemplo están adjuntos a anfitrión elemento. El contenido de la ranura en realidad pertenece al DOM «Luz», no a la Sombra Dom. A diferencia de los ejemplos que hemos cubierto hasta ahora, estos elementos pueden ser directamente de document Objetivo:
const widgetTitle = document.querySelector('my-widget (slot=title)');
widgetTitle.textContent="A Different Title";
Si desea acceder a estos elementos desde la definición de clase, use this.children o this.querySelector. solo <slot> El elemento en sí se puede consultar sombreando el DOM en lugar de su contenido.
De misterio a maestro
Ahora sabes Por qué Vas a usar Shadow Dom, cuando Debe incluirlo en su trabajo y cómo Puedes usarlo ahora.
Pero el viaje de su componente de red no puede terminar aquí. En este artículo, solo cubrimos etiquetas y scripts. Ni siquiera tocamos otro aspecto importante de los componentes web: Encapsulación de estilo. Este será un tema en otro artículo en nuestro artículo.
(GG, YK)