El beneficio de los componentes web es que se pueden reutilizar: puedes crear un widget de IU una vez y reutilizarlo varias veces. Mientras que necesitas JavaScript para crear componentes web, no necesitas una biblioteca de JavaScript. HTML y las APIs asociadas proporcionan todo lo que necesitas.
El estándar de componentes web consta de tres partes: plantillas HTML, Elementos personalizados y Shadow DOM. Cuando se combinan, permiten crear elementos personalizados, autónomos (encapsulados) y reutilizables que se pueden integrar a la perfección. en aplicaciones existentes, como todos los demás elementos HTML que hemos abarcado.
En esta sección, crearemos el elemento <star-rating>
, un componente web que permite a los usuarios calificar una experiencia en una
escala de una a cinco estrellas. Cuando se le asigna un nombre a un elemento personalizado, se recomienda usar solo letras minúsculas. Además, incluye un guion,
ya que ayuda a distinguir entre los elementos normales y personalizados.
Analizaremos cómo usar los elementos <template>
y <slot>
, el atributo slot
y JavaScript para crear una plantilla con
un Shadow DOM encapsulado. Luego, reutilizaremos el elemento definido y personalizaremos una sección de texto
como lo harías con cualquier elemento o componente web. También analizaremos brevemente el uso de CSS desde y fuera del elemento personalizado.
El elemento <template>
El elemento <template>
se usa para declarar fragmentos de HTML que se clonarán e insertarán en el DOM con JavaScript. El contenido del elemento no se renderiza de forma predeterminada. En cambio, se crean instancias de ellas con JavaScript.
<template id="star-rating-template">
<form>
<fieldset>
<legend>Rate your experience:</legend>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required />
<input type="radio" name="rating" value="2" aria-label="2 stars" />
<input type="radio" name="rating" value="3" aria-label="3 stars" />
<input type="radio" name="rating" value="4" aria-label="4 stars" />
<input type="radio" name="rating" value="5" aria-label="5 stars" />
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
Como el contenido de un elemento <template>
no se escribe en la pantalla, <form>
y su contenido no se renderizan.
Sí, este códec está vacío, pero si inspeccionas la pestaña HTML, verás el lenguaje de marcado <template>
.
En este ejemplo, <form>
no es un elemento secundario de <template>
en el DOM. En cambio, el contenido de los elementos <template>
es secundario
de un DocumentFragment
mostrado por HTMLTemplateElement.content
propiedad. Para que sea visible, se debe usar JavaScript para tomar el contenido y agregarlo al DOM.
Este código breve de JavaScript no creó un elemento personalizado. En cambio, en este ejemplo, se agregó el contenido de <template>
a <body>
.
El contenido ahora forma parte del DOM visible y con estilo.
No es muy útil solicitar JavaScript para implementar una plantilla solo para una calificación por estrellas, pero crear un componente web para una se usa repetidamente, el widget personalizable de calificación por estrellas es útil.
El elemento <slot>
Incluimos un espacio para incluir una leyenda personalizada por caso. HTML proporciona un <slot>
como marcador de posición dentro de un <template>
que, si se proporciona un nombre, crea un "ranura con nombre". Se puede usar una ranura con nombre
para personalizar el contenido dentro de un componente web. El elemento <slot>
nos permite controlar el origen de los elementos secundarios de una
elemento debe insertarse dentro de su shadow tree.
En nuestra plantilla, cambiamos <legend>
por <slot>
:
<template id="star-rating-template">
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
El atributo name
se usa para asignar ranuras a otros elementos si el elemento tiene un atributo slot cuyo valor coincide con el
el nombre de una ranura con nombre. Si el elemento personalizado no tiene coincidencias para un espacio, se renderizará el contenido de <slot>
.
Por lo tanto, incluimos un <legend>
con contenido genérico que se puede procesar si alguien simplemente incluye <star-rating></star-rating>
, sin contenido, en su HTML.
<star-rating>
<legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Toasty McToastface</legend>
<p>Is this text visible?</p>
</star-rating>
El atributo slot es un atributo global que se usa
para reemplazar el contenido de <slot>
dentro de un <template>
. En nuestro elemento personalizado, el elemento con el atributo de espacio
es un <legend>
. No tiene por qué serlo. En nuestra plantilla, <slot name="star-rating-legend">
se reemplazará por <anyElement slot="star-rating-legend">
,
donde <anyElement>
puede ser cualquier elemento, incluso otro elemento personalizado.
Elementos sin definir
En nuestra <template>
, usamos un elemento <rating>
. Este no es un elemento personalizado. Más bien, es un elemento desconocido. Navegadores
no fallan cuando no reconocen un elemento. El navegador trata los elementos HTML no reconocidos como intercalados anónimos.
elementos a los que se les puede aplicar estilo con CSS. Al igual que <span>
, los elementos <rating>
y <star-rating>
no tienen un usuario-agente aplicado.
estilos o semánticas.
Ten en cuenta que <template>
y el contenido no se renderizan. El <template>
es un elemento conocido que incluye contenido que
no debe renderizarse. Aún no se definió el elemento <star-rating>
. Hasta que definamos un elemento, el navegador lo mostrará
como todos los elementos no reconocidos. Por ahora, el elemento <star-rating>
no reconocido se trata como un elemento intercalado anónimo, por lo que el contenido
incluidas las leyendas y la <p>
del tercer <star-rating>
se muestran como se mostrarían si estuvieran en un <span>
.
Definamos nuestro elemento para convertir este elemento no reconocido en un elemento personalizado.
Elementos personalizados
Se requiere JavaScript para definir elementos personalizados. Cuando se define, el contenido del elemento <star-rating>
se reemplaza por un elemento
shadow root que contiene todos los contenidos de la plantilla que asociamos con ella. Se reemplazan los elementos <slot>
de la plantilla
con el contenido del elemento dentro de <star-rating>
cuyo valor de atributo slot
coincida con el valor de nombre de <slot>
, si
solo hay uno. De lo contrario, se muestra el contenido de las ranuras de la plantilla.
En un elemento personalizado, el contenido de un elemento personalizado que no está asociado a un espacio (el <p>Is this text visible?</p>
de nuestro tercer <star-rating>
) no se incluye en
la shadow root y, por lo tanto, no se muestra.
Definimos el elemento personalizado llamado star-rating
.
extendiendo HTMLElement
:
customElements.define('star-rating',
class extends HTMLElement {
constructor() {
super(); // Always call super first in constructor
const starRating = document.getElementById('star-rating-template').content;
const shadowRoot = this.attachShadow({
mode: 'open'
});
shadowRoot.appendChild(starRating.cloneNode(true));
}
});
Ahora que el elemento está definido, cada vez que el navegador encuentre un elemento <star-rating>
, se renderizará como se definió
por el elemento con #star-rating-template
, que es nuestra plantilla. El navegador adjuntará un árbol del shadow DOM al nodo y anexar
es una clonación del contenido de la plantilla en ese shadow DOM.
Ten en cuenta que los elementos sobre los que puedes attachShadow()
son limitados.
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));
Si observas las herramientas para desarrolladores, notarás que <form>
de <template>
forma parte de la raíz secundaria de cada elemento personalizado.
Una clonación del contenido de <template>
es visible en cada elemento personalizado en las herramientas para desarrolladores y visible en el navegador, pero
del elemento personalizado en sí no se renderizan en la pantalla.
En el ejemplo de <template>
, agregamos el contenido de la plantilla al cuerpo del documento y lo agregamos al DOM normal.
En la definición de customElements
, usamos lo mismo
appendChild()
, pero el contenido de la plantilla clonada se agregó a una
shadow DOM encapsulado.
¿Notas que las estrellas volvieron a ser botones de selección sin estilo? Por ser parte de un shadow DOM y no de un DOM estándar, no se aplica el estilo de la pestaña CSS de Codepen. El CSS de esa pestaña los estilos se definen en el documento y no en el shadow DOM, por lo que no se aplican. Tenemos que crear conjuntos de datos para definir el estilo de nuestro contenido de Shadow DOM encapsulado.
Shadow DOM
El Shadow DOM establece el alcance de los estilos CSS en cada shadow tree y lo aísla del resto del documento. Esto significa que los CSS externos no se aplica a tu componente, y los estilos de componente no tienen efecto en el resto del documento, a menos que intencionadamente dirigirlos.
Como agregamos el contenido a un shadow DOM, podemos incluir un elemento <style>
.
lo que proporciona CSS encapsulada al elemento personalizado.
Cuando se limita el alcance del elemento personalizado, no tenemos que preocuparnos de que los estilos se filtren en el resto del documento. Podemos
reducir sustancialmente la especificidad de los selectores. Por ejemplo, como las únicas entradas que se usan en el elemento personalizado son los botones de
botones, podemos usar input
en lugar de input[type="radio"]
como selector.
<template id="star-rating-template">
<style>
rating {
display: inline-flex;
}
input {
appearance: none;
margin: 0;
box-shadow: none;
}
input::after {
content: '\2605'; /* solid star */
font-size: 32px;
}
rating:hover input:invalid::after,
rating:focus-within input:invalid::after {
color: #888;
}
input:invalid::after,
rating:hover input:hover ~ input:invalid::after,
input:focus ~ input:invalid::after {
color: #ddd;
}
input:valid {
color: orange;
}
input:checked ~ input:not(:checked)::after {
color: #ccc;
content: '\2606'; /* hollow star */
}
</style>
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required/>
<input type="radio" name="rating" value="2" aria-label="2 stars"/>
<input type="radio" name="rating" value="3" aria-label="3 stars"/>
<input type="radio" name="rating" value="4" aria-label="4 stars"/>
<input type="radio" name="rating" value="5" aria-label="5 stars"/>
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
Mientras que los componentes web se encapsulan con lenguaje de marcado en <template>
, y los estilos CSS se definen en el shadow DOM y se ocultan
de todo lo que está fuera de los componentes, el contenido del espacio que se renderiza, la <anyElement slot="star-rating-legend">
de la <star-rating>
no está encapsulada.
Un diseño fuera del alcance actual
Es posible, aunque no sencillo, darle estilo al documento desde un shadow DOM y aplicar ajustes de estilo al contenido de un shadow DOM a partir del los estilos globales. El límite de las sombras, donde termina el shadow DOM y comienza el DOM normal, se puede recorrer, pero solo de forma muy intencional.
El shadow tree es el árbol del DOM dentro del shadow DOM. La shadow root es el nodo raíz del shadow tree.
La pseudoclase :host
selecciona el <star-rating>
, el elemento del host de sombra.
El shadow host es el nodo del DOM al que se adjunta el shadow DOM. Para establecer la segmentación solo a versiones específicas del host, usa :host()
.
Esto seleccionará solo los elementos del host paralelo que coincidan con el parámetro que se pasó, como un selector de clase o atributo. Para seleccionar
todos los elementos personalizados, puedes usar star-rating { /* styles */ }
en el CSS global o :host(:not(#nonExistantId))
en los estilos de plantilla. En términos
de especificidad, el CSS global gana.
El seudoelemento ::slotted()
cruza el límite del shadow DOM.
desde el shadow DOM. Selecciona un elemento con ranuras si coincide con el selector. En nuestro ejemplo, ::slotted(legend)
coincide con nuestras tres leyendas.
Para apuntar a un shadow DOM de CSS en el alcance global, es necesario editar la plantilla. La part
puedes agregar a cualquier elemento al que desees aplicar diseño. Luego, usa el seudoelemento ::part()
.
para hacer coincidir elementos dentro de un shadow tree que coinciden con el parámetro pasado. El ancla u elemento de origen del seudoelemento es
el nombre del host o del elemento personalizado, en este caso, star-rating
. El parámetro es el valor del atributo part
.
Si el lenguaje de marcado de nuestra plantilla comenzara así:
<template id="star-rating-template">
<form part="formPart">
<fieldset part="fieldsetPart">
Podríamos orientar a <form>
y <fieldset>
con lo siguiente:
star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }
Los nombres de las partes actúan de forma similar a las clases: un elemento puede tener varios nombres de partes separados por espacios y varios elementos pueden tienen el mismo nombre de pieza.
Google tiene una lista de tareas fantástica para crear elementos personalizados. También puedes aprender sobre los shadow DOM declarativos.
Verifica tus conocimientos
Pon a prueba tus conocimientos sobre plantillas, ranuras y sombras.
De forma predeterminada, los estilos externos al shadow DOM darán estilo a los elementos que están adentro.
¿Cuál de las siguientes respuestas es una descripción correcta del elemento <template>
?