Cómo funciona JS: tecnología Shadow DOM y componentes web


Hoy, en la traducción de 17 partes de los materiales dedicados a las características de todo lo que de alguna manera está conectado con JavaScript, hablaremos sobre componentes web y varios estándares que están destinados a trabajar con ellos. Se prestará especial atención a la tecnología Shadow DOM.



Revisar


Los componentes web son una familia de API diseñadas para describir nuevos elementos DOM adecuados para su reutilización. La funcionalidad de dichos elementos está separada del resto del código; se pueden usar en aplicaciones web de nuestro propio diseño.

Existen cuatro tecnologías relacionadas con los componentes web:

  • Shadow DOM (Shadow DOM)
  • Plantillas HTML (Plantillas HTML)
  • Elementos personalizados
  • Importaciones HTML (Importación HTML)

En este artículo, hablaremos sobre la tecnología Shadow DOM, que está diseñada para crear aplicaciones basadas en componentes. Ofrece formas de resolver problemas comunes de desarrollo web que quizás ya haya encontrado:

  • Aislamiento DOM: el componente tiene un árbol DOM aislado (esto significa que el comando document.querySelector() no permitirá el acceso al nodo en el DOM sombra del componente). Además, simplifica el sistema selector CSS en aplicaciones web, ya que los componentes DOM están aislados, lo que permite al desarrollador usar los mismos identificadores universales y nombres de clase en diferentes componentes sin preocuparse por posibles conflictos de nombres.
  • Aislamiento CSS: las reglas CSS descritas dentro del shadow DOM están limitadas a él. Estos estilos no abandonan el elemento, no se mezclan con otros estilos de página.
  • Composición: Desarrollo de una API declarativa para componentes basados ​​en marcado.

Tecnología Shadow DOM


Se supone que ya está familiarizado con el concepto de DOM y las API asociadas. Si esto no es así, puede leer este material.

Shadow DOM es básicamente lo mismo que un DOM normal, pero con dos diferencias:

  • El primero es cómo se crea y se usa el Shadow DOM, en particular, se trata de la relación del Shadow DOM con el resto de la página.
  • El segundo es el comportamiento del Shadow DOM en relación con la página.

Cuando se trabaja con DOM, se crean nodos DOM que se unen, como elementos secundarios, a otros elementos de la página. En el caso de la tecnología Shadow DOM, se crea un árbol DOM aislado que une el elemento, pero está separado de sus elementos secundarios normales.

Este subárbol aislado se llama árbol de sombra. El elemento al que se adjunta dicho árbol se llama host host. Todo lo que se agrega al subárbol DOM de la sombra resulta ser local para el elemento al que está adjunto, incluidos los estilos descritos con etiquetas <style> . Así es como se proporciona el aislamiento CSS a través de la tecnología Shadow DOM.

Crear un DOM de sombra


Shadow root es una parte del documento que se adjunta al elemento host. Un elemento adquiere un DOM de sombra cuando se le adjunta un elemento raíz de sombra. Para crear un DOM de sombra para un determinado elemento, debe usar un comando del formulario element.attachShadow() :

 var header = document.createElement('header'); var shadowRoot = header.attachShadow({mode: 'open'}); shadowRoot.appendChild(document.createElement('<p> Shadow DOM </p>'); 

Cabe señalar que en la especificación de DOM de sombra hay una lista de elementos a los que no se pueden conectar los subárboles de sombra de DOM.

Composición en Shadow DOM


La composición es una de las características más importantes de Shadow DOM, es una forma de crear aplicaciones web, que se utiliza en el proceso de escritura de código HTML. Durante este proceso, el programador combina los diversos bloques de construcción (elementos) que conforman la página, anidándolos, si es necesario, entre sí. Por ejemplo, estos son elementos como <div> , <header> , <form> y otros utilizados para crear interfaces de aplicaciones web, incluidos aquellos que actúan como contenedores para otros elementos.

La composición determina la capacidad de los elementos, como <select> , <form> , <video> , para incluir otros elementos HTML como elementos secundarios, y la capacidad de organizar comportamientos especiales de tales estructuras que consisten en diferentes elementos.

Por ejemplo, el elemento <select> tiene medios para representar elementos <option> en forma de una lista desplegable con el contenido predeterminado de los elementos de dicha lista.

Considere algunas de las características del Shadow DOM que se usa en la composición de elementos.

Dom ligero


Light DOM es el marcado creado por el usuario de su componente. Este DOM está fuera del DOM oculto del componente y es un hijo del componente. Imagine que creó un componente personalizado llamado <better-button> que amplía las capacidades del elemento HTML estándar <button> , y el usuario necesita agregar una imagen y algo de texto a este nuevo elemento. Así es como se ve:

 <extended-button> <!--  img  span -  Light DOM  extended-button --> <img align="center" src="boot.png" slot="image"> <span>Launch</span> </extended-button> 

El elemento <extended-button> es un componente personalizado descrito por el programador por sí mismo, y el código HTML dentro de este componente es su Light DOM, lo que el usuario de este componente le agregó.

La sombra DOM en este ejemplo es el componente <extended-button> . Este es un modelo de objeto local de un componente que describe su estructura interna, aislada del mundo exterior de CSS, y encapsula los detalles de implementación del componente.

Dom aplanado


El árbol de DOM aplanado representa cómo el navegador muestra el componente en la pantalla, combinando el DOM de luz y el DOM de sombra. Es un árbol DOM que se puede ver en las herramientas del desarrollador, y es el que se muestra en la página. Puede verse más o menos así:

 <extended-button> #shadow-root <style></style> <slot name="image">   <img align="center" src="boot.png" slot="image"> </slot> <span id="container">   <slot>     <span>Launch</span>   </slot> </span> </extended-button> 

Patrones


Si tiene que usar constantemente las mismas estructuras en el marcado HTML de las páginas web, será útil usar una plantilla determinada en lugar de escribir el mismo código una y otra vez. Esto era posible antes, pero ahora todo se ha simplificado enormemente gracias a la aparición de la etiqueta HTML <template> , que goza de un excelente soporte para los navegadores modernos. Este elemento y su contenido no se muestran en el DOM, pero puede trabajar con él desde JavaScript. Considere un ejemplo simple:

 <template id="my-paragraph"> <p> Paragraph content. </p> </template> 

Si incluye este diseño en el marcado HTML de la página, el contenido de la etiqueta <p> descrita por él no aparecerá en la pantalla hasta que se adjunte explícitamente al DOM del documento. Por ejemplo, podría verse así:

 var template = document.getElementById('my-paragraph'); var templateContent = template.content; document.body.appendChild(templateContent); 

Hay otros medios para lograr el mismo efecto, pero, como ya se mencionó, las plantillas son una herramienta estándar muy conveniente que goza de un buen soporte para el navegador.


Soporte de navegador HTML para navegadores modernos

Las plantillas son útiles en sí mismas, pero sus capacidades se revelan completamente cuando se usan con elementos personalizados. Los elementos personalizados son un tema para un material separado, y ahora, para comprender lo que está sucediendo, es suficiente tener en cuenta que la customElement navegadores customElement permite al programador describir sus propias etiquetas HTML y especificar cómo se verán en la pantalla los elementos creados con estas etiquetas.

Defina un componente web que use nuestra plantilla como contenido para su DOM sombra. Llame a este nuevo elemento <my-paragraph> :

 customElements.define('my-paragraph', class extends HTMLElement {  constructor() {    super();    let template = document.getElementById('my-paragraph');    let templateContent = template.content;    const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true)); } }); 

Lo más importante a lo que debe prestar atención es que adjuntamos un clon del contenido de la plantilla utilizando el método Node.cloneNode () a la raíz de la sombra.

Dado que adjuntamos el contenido de la plantilla al DOM de sombra, podemos incluir información de estilo en la plantilla en el elemento <style> , que luego se encapsulará en el elemento de usuario. Todo este esquema no funcionará como se esperaba si trabaja con el DOM regular en lugar del DOM de la sombra.

Por ejemplo, una plantilla se puede modificar de la siguiente manera al incluir información de estilo en ella:

 <template id="my-paragraph"> <style>   p {     color: white;     background-color: #666;     padding: 5px;   } </style> <p>Paragraph content. </p> </template> 

Ahora el elemento de usuario descrito por nosotros se puede usar en las páginas web normales de la siguiente manera:

 <my-paragraph></my-paragraph> 

Tragamonedas


Las plantillas HTML tienen varios inconvenientes, el principal es que las plantillas contienen marcado estático, que no permite, por ejemplo, mostrar el contenido de ciertas variables con su ayuda para trabajar con ellas de la misma manera que funcionan con HTML estándar patrones Aquí es donde entra la etiqueta <slot> .

Las ranuras se pueden percibir como marcadores de posición que le permiten incluir su propio código HTML en la plantilla. Esto le permite crear plantillas HTML universales y luego personalizarlas agregando ranuras a ellas.

Eche un vistazo a cómo se verá la plantilla anterior usando la <slot> :

 <template id="my-paragraph"> <p>   <slot name="my-text">Default text</slot> </p> </template> 

Si el contenido de la ranura no se especifica cuando el elemento se incluye en el marcado, o si el navegador no admite trabajar con ranuras, el elemento <my-paragraph> incluirá solo el contenido estándar del Default text .

Para establecer el contenido del espacio, debe incluir el código HTML con el atributo de slot en el elemento <my-paragraph> , cuyo valor es equivalente al nombre del espacio en el que desea colocar este código.

Como antes, puede haber cualquier cosa. Por ejemplo:

 <my-paragraph> <span slot="my-text">Let's have some different text!</span> </my-paragraph> 

Los elementos que se pueden colocar en las ranuras se denominan elementos deslizables .

Tenga en cuenta que en el ejemplo anterior agregamos el elemento <span> a la ranura, es el llamado elemento ranurado. Tiene un atributo de slot que se le asigna el valor my-text , es decir, el mismo valor que se usa en el atributo de name de la ranura descrita en la plantilla.

Después de procesar el marcado anterior, el navegador creará el siguiente árbol DOM aplanado:

 <my-paragraph> #shadow-root <p>   <slot name="my-text">     <span slot="my-text">Let's have some different text!</span>   </slot> </p> </my-paragraph> 

Presta atención al elemento #shadow-root . Esto es solo un indicador de la existencia del Shadow DOM.

Estilización


Los componentes que usan la tecnología Shadow DOM se pueden diseñar de forma común, pueden definir sus propios estilos o proporcionar ganchos en forma de propiedades CSS personalizadas que permiten a los usuarios de componentes anular los estilos predeterminados.

▍ Estilos descritos en componentes


El aislamiento CSS es una de las características más notables de la tecnología Shadow DOM. A saber, estamos hablando de lo siguiente:

  • Los selectores CSS de la página en la que se coloca el componente correspondiente no afectan lo que tiene dentro.
  • Los estilos descritos dentro del componente no afectan la página. Están aislados en el elemento host.

Los selectores CSS utilizados dentro del shadow DOM se aplican localmente al contenido del componente. En la práctica, esto significa la capacidad de reutilizar los mismos identificadores y nombres de clase en diferentes componentes y no hay que preocuparse por los conflictos de nombres. Los selectores CSS simples también significan un mejor rendimiento para las soluciones en las que se utilizan.

Eche un vistazo al elemento #shadow-root , que define algunos estilos:

 #shadow-root <style> #container {   background: white; } #container-items {   display: inline-flex; } </style> <div id="container"></div> <div id="container-items"></div> 

Todos los estilos anteriores son locales para #shadow-root .

Además, puede usar la etiqueta <link> para incluir hojas de estilo externas en #shadow-root . Dichos estilos también serán locales.

▍Pseudoclase: host


La pseudoclase :host permite acceder a un elemento que contiene un árbol DOM de sombra y darle estilo a este elemento:

 <style> :host {   display: block; /*       display: inline */ } </style> 

Con la pseudoclase :host , recuerde que las reglas de la página principal tienen mayor prioridad que las especificadas en el elemento que utiliza esta pseudoclase. Esto permite a los usuarios anular los estilos de componentes del host definidos desde afuera. Además, la pseudoclase :host solo funciona en el contexto del elemento raíz sombra; no puede usarlo fuera del árbol DOM sombra.

La forma funcional de la pseudoclase ,: :host(<selector>) , le permite acceder al elemento host si coincide con el elemento <selector> especificado. Esta es una excelente manera de permitir que los componentes encapsulen el comportamiento que responde a las acciones del usuario o los cambios en el estado de un componente, y le permite diseñar nodos internos basados ​​en el componente host:

 <style> :host {   opacity: 0.4; } :host(:hover) {   opacity: 1; } :host([disabled]) { /*      -  disabled. */   background: grey;   pointer-events: none;   opacity: 0.4; } :host(.pink) > #tabs {   color: pink; /*     #tabs   -  class="pink". */ } </style> 

▍Tópicos y elementos con una pseudoclase: host-context (<selector>)


La pseudo- :host-context(<selector>) coincide con el elemento host si éste o alguno de sus antepasados ​​coincide con el elemento <selector> especificado.

Un caso de uso común para esta característica es diseñar elementos con temas. Por ejemplo, los temas se usan a menudo asignando la clase apropiada a las etiquetas <html> o <body> :

 <body class="lightheme"> <custom-container></custom-container> </body> 

La :host-context(.lightheme) se aplicará a <fancy-tabs> si este elemento es un descendiente de .lightteme :

 :host-context(.lightheme) { color: black; background: white; } 

La construcción :host-context() puede ser útil para aplicar temas, pero para este propósito es mejor usar ganchos usando propiedades CSS personalizadas .

▍ Diseñar el elemento host del componente desde el exterior


El elemento host del componente se puede diseñar externamente utilizando el nombre de su etiqueta como selector:

 custom-container { color: red; } 

Los estilos externos tienen prioridad sobre los estilos definidos en la sombra DOM.
Supongamos que un usuario crea el siguiente selector:

 custom-container { width: 500px; } 

Anulará la regla definida en el componente mismo:

 :host { width: 300px; } 

Con este enfoque, puede estilizar solo el componente en sí. ¿Cómo estilizar la estructura interna de un componente? Se utilizan propiedades CSS personalizadas para este propósito.

▍Creando ganchos de estilo usando propiedades CSS personalizadas


Los usuarios pueden personalizar los estilos de las estructuras internas de los componentes si el autor del componente les proporciona enlaces de estilo utilizando propiedades CSS personalizadas .

Este enfoque se basa en un mecanismo similar al utilizado cuando se trabaja con etiquetas <slot> , pero, en este caso, se aplica a los estilos.

Considere un ejemplo:

 <!-- main page --> <style> custom-container {   margin-bottom: 60px;    - custom-container-bg: black; } </style> <custom-container background></custom-container> 

Esto es lo que hay dentro del árbol DOM de sombra:

 :host([background]) { background: var( - custom-container-bg, #CECECE); border-radius: 10px; padding: 10px; } 

En este caso, el componente utiliza negro como color de fondo, ya que fue el usuario quien lo especificó. De lo contrario, el color de fondo será #CECECE .

Como autor del componente, usted es responsable de informar a sus usuarios qué propiedades CSS específicas pueden usar. Considere esta parte de la interfaz abierta de su componente.

API de JavaScript para trabajar con slots


La API Shadow DOM proporciona la capacidad de trabajar con slots.

▍ Cambio de ranura de evento


El evento de cambio de ranura se slotchange cuando cambian los nodos ubicados en la ranura. Por ejemplo, si un usuario agrega o elimina nodos secundarios en Light DOM:

 var slot = this.shadowRoot.querySelector('#some_slot'); slot.addEventListener('slotchange', function(e) { console.log('Light DOM change'); }); 

Para rastrear otros tipos de cambios en Light DOM, puede usar MutationObserver en el constructor del elemento. Lea más sobre esto aquí .

▍ Método asignadoNodos ()


El método assignedNodes() puede ser útil si necesita saber qué elementos están asociados con la ranura. Llamar al método slot.assignedNodes() permite averiguar exactamente qué elementos muestra la ranura. El uso de la opción {flatten: true} permite obtener el contenido estándar de la ranura (que se muestra si no se le asignaron nodos).

Considere un ejemplo:

 <slot name='slot1'><p>Default content</p></slot> 

Imagine que esta ranura se encuentra en el componente <my-container> .

Echemos un vistazo a los diversos usos de este componente y lo que se devolverá cuando se llame al método assignedNodes() .

En el primer caso, agregamos nuestro propio contenido a la ranura:

 <my-container> <span slot="slot1"> container text </span> </my-container> 

En este caso, la llamada assignedNodes() devolverá [ container text ] . Tenga en cuenta que este valor es una matriz de nodos.

En el segundo caso, no llenamos el espacio con nuestro propio contenido:

 <my-container> </my-container> 

La llamada assignedNodes() devolverá una matriz vacía - [] .

Sin embargo, si pasa el parámetro {flatten: true} a este método, entonces llamarlo para el mismo elemento devolverá su contenido predeterminado: [ Default content ]

[ Default content ]

[ Default content ]

Además, para acceder a un elemento dentro de la ranura, puede llamar a assignedNodes() para informarle a qué ranura de componente está asignado su elemento.

Modelo de evento


Hablemos de lo que sucede cuando aparece un evento que aparece en la sombra del árbol DOM. El propósito del evento se establece teniendo en cuenta la encapsulación admitida por la tecnología Shadow DOM. Cuando se redirige un evento, parece que proviene del componente en sí y no de su elemento interno, que se encuentra en el árbol DOM de la sombra y es parte de este componente.

Aquí hay una lista de eventos que se pasan del árbol de sombra DOM (este comportamiento no es característico de algunos eventos):

  • Eventos de enfoque: blur , focus , focusin , focusout .
  • Eventos del mouse s: click , dblclick , mousedown , mouseenter , mousemove y otros.
  • Eventos de wheel : wheel .
  • Eventos de entrada: beforeinput input , input .
  • Eventos de teclado: keydown , keyup .
  • Eventos de compositionstart : inicio de compositionstart , compositionupdate compositionstart , compositionupdate compositionend .
  • Eventos de arrastre: dragstart , drag , dragend , drop , etc.

Eventos personalizados


Los eventos de usuario de forma predeterminada no dejan el árbol de sombra DOM. Si desea desencadenar un evento y desea que abandone el DOM DOM, debe proporcionarle las bubbles: true parámetros bubbles: true y composed: true . Así es como se ve la llamada de tal evento:

 var container = this.shadowRoot.querySelector('#container'); container.dispatchEvent(new Event('containerchanged', {bubbles: true, composed: true})); 

Soporte para navegadores Shadow DOM


Para saber si el navegador admite la tecnología Shadow DOM, puede verificar la presencia de attachShadow :

 const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow; 

Aquí hay información sobre cómo varios navegadores admiten esta tecnología.


Soporte para la tecnología Shadow DOM en navegadores

Resumen


El árbol DOM de sombra no se comporta como un árbol DOM normal. En particular, según el autor de este material, en la biblioteca SessionStack esto se expresa en la complicación del procedimiento para rastrear los cambios DOM, información sobre la cual se necesita para reproducir lo que sucedió con la página. A saber, MutationObserver usa para rastrear cambios. En este caso, el árbol de sombra DOM no genera el evento MutationObserver en el ámbito global, lo que lleva a la necesidad de utilizar enfoques especiales para trabajar con componentes que usan el DOM sombra.

, - Shadow DOM, , , , .

Estimados lectores! -, Shadow DOM?

Source: https://habr.com/ru/post/es415881/


All Articles