Shadow DOM de Web Components

  • Por
Explicaciones y ejemplos de uso de Shadow DOM el estándar de Web Components que nos sirve para crear elementos del DOM encapsulados en otros elementos de la página.

De todas las especificaciones de los Web Components, el estándar de la W3C para el desarrollo de componentes modulares y completamente reutilizables, Shadow DOM nos ofrece los mecanismos más importantes para que los módulos sean realmente autónomos e independientes de otros elementos de la página. En síntesis, Shadow DOM permite insertar elementos dentro del DOM de la página, pero sin exponerlos hacia afuera, de modo que no se puedan tocar accidentalmente.

Cuando creamos un custom element a menudo éste necesita generar nuevos elementos, como botones, campos de texto, iconos, párrafos, que colocará debajo de su jerarquía. Todos esos elementos que cree el custom element podremos decir que le pertenecen directamente. El custom element dueño de sus elementos podrá, o no, ocultarlos de modo que no se puedan acceder desde fuera. Si se decide ocultar o encapsular esos elementos se usará el Shadow DOM. En ese caso, los elementos estarán físicamente en el DOM de la página, dependiendo únicamente del custom element que los ha generado y solo se podrán manipular por su dueño.

Básicamente, este DOM oculto a otros elementos de la página es el que nos permite aislar los componentes, produciendo la deseada encapsulación. El beneficio básico es que, al usar un custom element en la página, su contenido encapsulado no podrá interaccionar con otros elementos de fuera, evitando daños colaterales: Sobre todo, otros elementos de la página no podrán romper el estilo o comportamiento del custom element que usó el Shadow DOM.

Nota: Quizás hayas experimentado alguna vez la desagradable situación que al insertar un plugin jQuery éste rompe estilos en tu página. O el componente no funciona porque otras partes de tu código interaccionan con él, u otros estilos CSS que tenías declarados de manera global. Todo esto está solucionado en los Web Components y mucho depende directamente de la especificación de Shadow DOM.

Crear Shadow DOM con Javascript

Ahora vamos a crear unos ejemplos básicos en los que usaremos la especificación de Shadow DOM para que, mediante Javascript, podamos crear e inyectar nuevos elementos en el DOM de la página, pero posibilitando que estén ocultos.

Nota: Como otras especificaciones de los Web Components, podemos usar Shadow DOM sin necesidad de utilizarlo en conjunto con otras especificaciones como la de Custom Elements. Sin embargo, lo cierto es que cobra especial sentido y utilidad cuando usamos varias de las especificaciones en conjunto. Por ello, el siguiente ejemplo tiene sobre todo valor didáctico, pero no ilustra del todo su uso más habitual. Veremos ejemplos que usen el Shadow DOM junto con otras especificaciones más adelante, siendo que en este artículo en el último ejemplo mezclaremos la especificación de Template y la de Shadow DOM.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Shadow DOM</title>
<style>
		p{
			color: red;
		}
	</style>
</head>
<body>
	<div id="elem"></div>

	<script>
	//un elemento de la página
	var elem = document.querySelector("#elem");

	//convertimos ese elemento en un "shadow root"
	var shadow = elem.createShadowRoot();

	//cambiamos el contenido de ese elemento, esto será creado como "shadow DOM"
	shadow.innerHTML = "<p>Esto va al shadow DOM</p>";
	</script>
</body>
</html>

Para entender el ejemplo hemos colocado varios comentarios. No obstante, lo explicamos de nuevo. Primero tenemos un elemento de la página al que le hemos colocado un identificador, solamente para luego poder referirnos a él: id="elem".

Luego accedemos a ese elemento mediante Javascript:

var elem = document.querySelector("#elem");

Más tarde, cuando ya hemos decidido que queremos usar Shadow DOM, tenemos que producir una raíz donde se va a insertar todo elemento que vaya a estar encapsulado. A esa raíz se la conoce como "Shadow Root" y se genera con la siguiente instrucción:

var shadow = elem.createShadowRoot();

Por último tenemos que añadir nuevos elementos dentro del Shadow Root y eso lo podemos hacer mediante varios mecanismos. Un ejemplo sería editar su propiedad innerHTML.

shadow.innerHTML = "<p>Esto va al shadow DOM</p>";

Ese párrafo no se podrá tocar desde fuera. Por eso, si te fijas, en la cabecera teníamos un estilo que aplicaría a todos los párrafos de la página y, sin embargo, a la hora de la verdad, no está afectando al párrafo que hemos colocado dentro del Shadow Root.

Nota: Como ves en este ejemplo, insistimos nuevamente, no es necesario incluir ningún tipo de librería adicional para que el navegador entienda el Shadow DOM. Sin embargo, de momento esto solo funcionará en Chrome y Opera que son los que más se han apresurado a cumplir el estándar. Por supuesto, si usas el correspondiente polyfill podrás ver el ejemplo funcionando también en otros navegadores. Sobre el Polyfill hablaremos también en detalle en artículos futuros, aunque también tenemos unas notas interesantes que aportar más tarde.

Pseudo elemento CSS ::shadow para acceso al Shadow DOM

Obviamente, en ocasiones conviene poder saltarse la regla y aplicar estilos a elementos que están dentro de shadow DOM. Para ello tenemos un selector llamado ::shadow. En realidad es un pseudo elemento que se usa anteponiendo al selector que queramos aplicar dentro de un nodo Shadow Root.

Para que el párrafo anterior estuviera afectado por el CSS tendríamos que usar ::shadow de la siguiente manera.

<style>
	::shadow p{
		color: red;
	}
</style>

Es una funcionalidad útil, aunque debes tener en cuenta que el pseudo elemento ::shadow se ha marcado como "depretated" (va a estar obsoleto y por tanto no se aplicará soporte en navegadores en adelante). No obstante, aunque este pseudoelemento sirva para saltarse el encapsulamiento entendemos que resulta bastante interesante, por lo que esperaríamos que se permita el uso de alguna alternativa similar para poder cubrir esta previsible necesidad.

Polyfill y Shadow DOM

Quizás la parte que resulta más complicado de simular meditante un polyfill, dentro de lo que respecta a los web components, es la de Shadow DOM. Por ello, el soporte a esta especificación de la W3C en los "polyfilled browsers" no es completo.

Por tanto, aunque el navegador muestre en la página aquellos elementos que se hayan colocado dentro de un Shadow Root, realmente no existirá esa mencionada encapsulación y se podrán tocar desde fuera, o alterar su aspecto con CSS definidos de manera global.

Ese motivo también hace que librerías como Polymer toquen el Shadow DOM, al menos por ahora, de una manera especial, no aportando todas las ventajas que tendría a priori en los navegadores que no lo soportan de manera nativa. Esto se hace para evitar afectar muy negativamente al rendimiento de las aplicaciones con Web Components y para que el comportamiento sea diferente en navegadores que lo implementan de manera nativa y los que no.

Shadow DOM que importamos desde un template

Antes de acabar, vamos a ver cómo alterar un poco nuestro ejemplo para que podamos usar un template, cuyo contenido se va a insertar como Shadow DOM. Recuerda que vimos templates de Web Components en un artículo anterior.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Shadow DOM</title>
</head>
<body>
	<div id="elem"></div>

	<template>
		<p>Esto es un template!!</p>
	</template>

	<script>
	//accedo al template
	var template = document.querySelector('template').content;
	//clono el contenido del template
	var clone = template.cloneNode(true);

	//accedo a un elemento
	var elem = document.querySelector("#elem");
	//creo el shadow root
	var shadow = elem.createShadowRoot();
	//le añado el clon del template
 	shadow.appendChild(clone);
	</script>
</body>
</html>

La diferencia es bien poca, simplemente tengo que acceder al template y clonar aquella parte que quiero usar dentro del Sadow DOM de otro elemento.

Luego ese clon del template es el que añado al Shadow Root con el método appendChild().

//creo el shadow root
var shadow = elem.createShadowRoot();
//le añado el clon del template
 shadow.appendChild(clone);

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir