Los elementos personalizados permiten a los desarrolladores web definir nuevas etiquetas HTML, extender las existentes y crear componentes web reutilizables.
Con los elementos personalizados, los desarrolladores web pueden crear etiquetas HTML nuevas, mejorar las etiquetas HTML existentes o extender los componentes que escribieron otros desarrolladores. La API es la base de los componentes web. Ofrece una forma basada en estándares web para crear componentes reutilizables con solo JS/HTML/CSS básicos. El resultado es menos código, código modular y más reutilización en nuestras apps.
Introducción
El navegador nos brinda una excelente herramienta para estructurar aplicaciones web. Se llama HTML. Es posible que hayas oído hablar de ella. Es declarativo, portátil, es compatible y es fácil de usar. Por muy bueno que sea el HTML, su vocabulario y su extensibilidad son limitados. El estándar activo de HTML siempre careció de una forma de asociar automáticamente el comportamiento de JS con tu marcado… hasta ahora.
Los elementos personalizados son la respuesta para modernizar el HTML, completar las piezas que faltan y agrupar la estructura con el comportamiento. Si el HTML no proporciona la solución a un problema, podemos crear un elemento personalizado que sí lo haga. Los elementos personalizados le enseñan al navegador nuevos trucos y, al mismo tiempo, conservan los beneficios del HTML.
Cómo definir un elemento nuevo
Para definir un nuevo elemento HTML, necesitamos la potencia de JavaScript.
El elemento global customElements
se usa para definir un elemento personalizado y enseñarle al navegador sobre una etiqueta nueva. Llama a customElements.define()
con el nombre de la etiqueta que deseas crear y un class
de JavaScript que extienda la HTMLElement
base.
Ejemplo: Definición de un panel de panel lateral para dispositivos móviles, <app-drawer>
:
class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});
Ejemplo de uso:
<app-drawer></app-drawer>
Es importante recordar que usar un elemento personalizado no es diferente que usar un <div>
o cualquier otro elemento. Las instancias se pueden declarar en la página, crear de forma dinámica en JavaScript, adjuntar objetos de escucha de eventos, etcétera. Sigue leyendo para ver más ejemplos.
Cómo definir la API de JavaScript de un elemento
La funcionalidad de un elemento personalizado se define con un class
de ES2015 que extiende HTMLElement
. La extensión de HTMLElement
garantiza que el elemento personalizado herede toda la API de DOM y que cualquier propiedad o método que agregues a la clase forme parte de la interfaz DOM del elemento. En esencia, usa la clase para crear una API pública de JavaScript para tu etiqueta.
Ejemplo: Definición de la interfaz DOM de <app-drawer>
:
class AppDrawer extends HTMLElement {
// A getter/setter for an open property.
get open() {
return this.hasAttribute('open');
}
set open(val) {
// Reflect the value of the open property as an HTML attribute.
if (val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
this.toggleDrawer();
}
// A getter/setter for a disabled property.
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of the disabled property as an HTML attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Can define constructor arguments if you wish.
constructor() {
// If you define a constructor, always call super() first!
// This is specific to CE and required by the spec.
super();
// Setup a click listener on <app-drawer> itself.
this.addEventListener('click', e => {
// Don't toggle the drawer if it's disabled.
if (this.disabled) {
return;
}
this.toggleDrawer();
});
}
toggleDrawer() {
// ...
}
}
customElements.define('app-drawer', AppDrawer);
En este ejemplo, creamos un panel lateral que tiene una propiedad open
, una propiedad disabled
y un método toggleDrawer()
. También refleja propiedades como atributos HTML.
Una característica interesante de los elementos personalizados es que this
dentro de una definición de clase se refiere al elemento DOM en sí, es decir, a la instancia de la clase. En nuestro ejemplo, this
hace referencia a <app-drawer>
. De esta manera (😉), el elemento puede vincular un objeto de escucha click
a sí mismo. Además, no te limitas a los objetos de escucha de eventos.
Toda la API de DOM está disponible dentro del código del elemento. Usa this
para acceder a las
propiedades del elemento, inspeccionar sus elementos secundarios (this.children
), consultar nodos
(this.querySelectorAll('.items')
), etcétera.
Reglas para crear elementos personalizados
- El nombre de un elemento personalizado debe contener un guion (-). Por lo tanto,
<x-tags>
,<my-element>
y<my-awesome-app>
son nombres válidos, mientras que<tabs>
y<foo_bar>
no lo son. Este requisito es para que el analizador de HTML pueda distinguir los elementos personalizados de los elementos normales. También garantiza la compatibilidad con versiones posteriores cuando se agregan etiquetas nuevas al código HTML. - No puedes registrar la misma etiqueta más de una vez. Si intentas hacerlo, se arrojará un
DOMException
. Una vez que le hayas informado al navegador sobre una etiqueta nueva, ya está. No se aceptan devoluciones. - Los elementos personalizados no pueden cerrarse automáticamente porque HTML solo permite que algunos elementos lo hagan. Siempre escribe una etiqueta de cierre (
<app-drawer></app-drawer>
).
Reacciones de elementos personalizados
Un elemento personalizado puede definir hooks de ciclo de vida especiales para ejecutar código durante momentos interesantes de su existencia. Estas se denominan reacciones de elementos personalizados.
Nombre | Se llama cuando |
---|---|
constructor |
Se crea o actualiza una instancia del elemento. Es útil para inicializar el estado, configurar objetos de escucha de eventos o crear un DOM sombreado.
Consulta las
especificaciones
para conocer las restricciones sobre lo que puedes hacer en constructor .
|
connectedCallback |
Se llama cada vez que se inserta el elemento en el DOM. Es útil para ejecutar código de configuración, como la recuperación de recursos o la renderización. Por lo general, debes intentar retrasar el trabajo hasta ese momento. |
disconnectedCallback |
Se llama cada vez que se quita el elemento del DOM. Es útil para ejecutar código de limpieza. |
attributeChangedCallback(attrName, oldVal, newVal) |
Se llama cuando se agrega, quita, actualiza o reemplaza un atributo observado. También se llama a los valores iniciales cuando el analizador crea un elemento o lo actualiza. Nota: Solo los atributos enumerados en la propiedad observedAttributes recibirán esta devolución de llamada.
|
adoptedCallback |
El elemento personalizado se trasladó a un document nuevo (p.ej., uno llamado document.adoptNode(el) ).
|
Las devoluciones de llamada de reacción son síncronas. Si alguien llama a el.setAttribute()
en tu elemento, el navegador llamará inmediatamente a attributeChangedCallback()
.
Del mismo modo, recibirás un disconnectedCallback()
justo después de que se quite
tu elemento del DOM (p.ej., el usuario llama a el.remove()
).
Ejemplo: Agrega reacciones de elementos personalizados a <app-drawer>
:
class AppDrawer extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// ...
}
connectedCallback() {
// ...
}
disconnectedCallback() {
// ...
}
attributeChangedCallback(attrName, oldVal, newVal) {
// ...
}
}
Define reacciones si es necesario. Si tu elemento es lo suficientemente complejo y abre una conexión a IndexedDB en connectedCallback()
, realiza la limpieza necesaria en disconnectedCallback()
. Pero ten cuidado. No puedes confiar en que tu elemento se quite del DOM en todas las circunstancias. Por ejemplo, nunca se llamará a disconnectedCallback()
si el usuario cierra la pestaña.
Propiedades y atributos
Cómo reflejar propiedades en atributos
Es común que las propiedades HTML reflejen su valor en el DOM como un atributo HTML. Por ejemplo, cuando se cambian los valores de hidden
o id
en JS:
div.id = 'my-id';
div.hidden = true;
los valores se aplican al DOM en vivo como atributos:
<div id="my-id" hidden>
Esto se denomina "reflejo de propiedades en atributos". Casi todas las propiedades en HTML hacen esto. ¿Por qué? Los atributos también son útiles para configurar un elemento de forma declarativa, y ciertas APIs, como los selectores de accesibilidad y CSS, dependen de los atributos para funcionar.
Reflejar una propiedad es útil en cualquier lugar donde desees mantener la representación del DOM del elemento sincronizada con su estado de JavaScript. Uno de los motivos por los que podrías querer reflejar una propiedad es para que se aplique el diseño definido por el usuario cuando cambie el estado de JS.
Consulta nuestro <app-drawer>
. Es posible que un consumidor de este componente desee atenuarlo o evitar la interacción del usuario cuando esté inhabilitado:
app-drawer[disabled] {
opacity: 0.5;
pointer-events: none;
}
Cuando se cambia la propiedad disabled
en JS, queremos que ese atributo se agregue al DOM para que coincida con el selector del usuario. El elemento puede proporcionar ese comportamiento reflejando el valor en un atributo con el mismo nombre:
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of `disabled` as an attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
this.toggleDrawer();
}
Observa los cambios en los atributos
Los atributos HTML son una forma conveniente para que los usuarios declaren el estado inicial:
<app-drawer open disabled></app-drawer>
Los elementos pueden reaccionar a los cambios de atributos definiendo un attributeChangedCallback
. El navegador llamará a este método por cada cambio en los atributos que se enumeran en el array observedAttributes
.
class AppDrawer extends HTMLElement {
// ...
static get observedAttributes() {
return ['disabled', 'open'];
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Only called for the disabled and open attributes due to observedAttributes
attributeChangedCallback(name, oldValue, newValue) {
// When the drawer is disabled, update keyboard/screen reader behavior.
if (this.disabled) {
this.setAttribute('tabindex', '-1');
this.setAttribute('aria-disabled', 'true');
} else {
this.setAttribute('tabindex', '0');
this.setAttribute('aria-disabled', 'false');
}
// TODO: also react to the open attribute changing.
}
}
En el ejemplo, configuramos atributos adicionales en <app-drawer>
cuando se cambia un atributo disabled
. Aunque no lo hacemos aquí, también puedes usar attributeChangedCallback
para mantener una propiedad JS sincronizada con su atributo.
Actualizaciones de elementos
HTML mejorado de forma progresiva
Ya aprendimos que los elementos personalizados se definen llamando a customElements.define()
. Pero esto no significa que debas definir y registrar un elemento personalizado todo de una vez.
Los elementos personalizados se pueden usar antes de que se registre su definición.
La mejora progresiva es una función de los elementos personalizados. En otras palabras, puedes declarar muchos elementos <app-drawer>
en la página y nunca invocar customElements.define('app-drawer', ...)
hasta mucho más tarde. Esto se debe a que el navegador trata los posibles elementos personalizados de manera diferente gracias a las etiquetas desconocidas. El proceso de llamar a define()
y dotar a un elemento existente con una definición de clase se denomina “actualizaciones de elementos”.
Para saber cuándo se define un nombre de etiqueta, puedes usar
window.customElements.whenDefined()
. Muestra una promesa que se resuelve cuando se define el elemento.
customElements.whenDefined('app-drawer').then(() => {
console.log('app-drawer defined');
});
Ejemplo: Retrasa el trabajo hasta que se actualice un conjunto de elementos secundarios
<share-buttons>
<social-button type="twitter"><a href="...">Twitter</a></social-button>
<social-button type="fb"><a href="...">Facebook</a></social-button>
<social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');
let promises = [...undefinedButtons].map((socialButton) => {
return customElements.whenDefined(socialButton.localName);
});
// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
// All social-button children are ready.
});
Contenido definido por elementos
Los elementos personalizados pueden administrar su propio contenido con las APIs de DOM dentro del código del elemento. Las reacciones son útiles para esto.
Ejemplo: Crea un elemento con HTML predeterminado:
customElements.define('x-foo-with-markup', class extends HTMLElement {
connectedCallback() {
this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
}
// ...
});
Si declaras esta etiqueta, ocurrirá lo siguiente:
<x-foo-with-markup>
<b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>
// TODO: DevSite: Se quitó el ejemplo de código porque usaba controladores de eventos intercalados
Crea un elemento que use Shadow DOM
Shadow DOM proporciona una forma para que un elemento posea, renderice y aplique diseño a un fragmento de DOM que está separado del resto de la página. Incluso puedes ocultar una app completa dentro de una sola etiqueta:
<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>
Para usar Shadow DOM en un elemento personalizado, llama a this.attachShadow
dentro de tu constructor
:
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<style>:host { ... }</style> <!-- look ma, scoped styles -->
<b>I'm in shadow dom!</b>
<slot></slot>
`;
customElements.define('x-foo-shadowdom', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
Ejemplo de uso:
<x-foo-shadowdom>
<p><b>User's</b> custom text</p>
</x-foo-shadowdom>
<!-- renders as -->
<x-foo-shadowdom>
#shadow-root
<b>I'm in shadow dom!</b>
<slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>
Texto personalizado del usuario
// TODO: DevSite: Se quitó el ejemplo de código porque usaba controladores de eventos intercalados
Cómo crear elementos a partir de un <template>
Para quienes no lo conocen, el elemento <template>
te permite declarar fragmentos del DOM que se analizan, se inactivan durante la carga de la página y se pueden activar más adelante durante el tiempo de ejecución. Es otra primitiva de API en la familia de componentes web. Las plantillas son un marcador de posición ideal para declarar la estructura de un elemento personalizado.
Ejemplo: Registra un elemento con contenido de Shadow DOM creado a partir de un <template>
:
<template id="x-foo-from-template">
<style>
p { color: green; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.</p>
</template>
<script>
let tmpl = document.querySelector('#x-foo-from-template');
// If your code is inside of an HTML Import you'll need to change the above line to:
// let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');
customElements.define('x-foo-from-template', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
</script>
Estas pocas líneas de código son muy potentes. Veamos los aspectos clave que están sucediendo:
- Definiremos un elemento nuevo en HTML:
<x-foo-from-template>
- El Shadow DOM del elemento se crea a partir de un
<template>
. - El DOM del elemento es local para el elemento gracias al Shadow DOM.
- El CSS interno del elemento tiene un alcance limitado al elemento gracias al Shadow DOM.
Estoy en Shadow DOM. Mi lenguaje de marcado se marcó desde una <template>.
// TODO: DevSite: Se quitó el ejemplo de código porque usaba controladores de eventos intercalados
Aplica diseño a un elemento personalizado
Incluso si tu elemento define su propio diseño con Shadow DOM, los usuarios pueden aplicarle un diseño a tu elemento personalizado desde su página. Estos se denominan "estilos definidos por el usuario".
<!-- user-defined styling -->
<style>
app-drawer {
display: flex;
}
panel-item {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
panel-item:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > panel-item {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-drawer>
<panel-item>Do</panel-item>
<panel-item>Re</panel-item>
<panel-item>Mi</panel-item>
</app-drawer>
Es posible que te preguntes cómo funciona la especificidad de CSS si el elemento tiene estilos definidos dentro de Shadow DOM. En términos de especificidad, los estilos de usuario son los más adecuados. Siempre anularán el diseño definido por el elemento. Consulta la sección sobre cómo crear un elemento que use Shadow DOM.
Aplica diseño a elementos no registrados previamente
Antes de que un elemento se actualice, puedes segmentarlo en CSS con la pseudoclase :defined
. Esto es útil para aplicar un diseño previo a un componente. Por ejemplo, es posible que desees ocultar los componentes no definidos y atenuarlos cuando se definan para evitar el diseño o cualquier otro FOUC visual.
Ejemplo: Oculta <app-drawer>
antes de que se defina:
app-drawer:not(:defined) {
/* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
display: inline-block;
height: 100vh;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
Después de que se define <app-drawer>
, el selector (app-drawer:not(:defined)
)
ya no coincide.
Cómo extender elementos
La API de Custom Elements es útil para crear elementos HTML nuevos, pero también para extender otros elementos personalizados o incluso el HTML integrado del navegador.
Cómo extender un elemento personalizado
Para extender otro elemento personalizado, se extiende su definición de clase.
Ejemplo: Crea <fancy-app-drawer>
que extiende <app-drawer>
:
class FancyDrawer extends AppDrawer {
constructor() {
super(); // always call super() first in the constructor. This also calls the extended class' constructor.
// ...
}
toggleDrawer() {
// Possibly different toggle implementation?
// Use ES2015 if you need to call the parent method.
// super.toggleDrawer()
}
anotherMethod() {
// ...
}
}
customElements.define('fancy-app-drawer', FancyDrawer);
Cómo extender elementos HTML nativos
Supongamos que quieres crear un <button>
más elegante. En lugar de replicar el comportamiento y la funcionalidad de <button>
, una mejor opción es mejorar de forma progresiva el elemento existente con elementos personalizados.
Un elemento integrado personalizado es un elemento personalizado que extiende una de las etiquetas HTML integradas del navegador. El beneficio principal de extender un elemento existente es obtener todas sus funciones (propiedades, métodos y accesibilidad del DOM). No hay mejor manera de escribir una app web progresiva que mejorar de forma progresiva los elementos HTML existentes.
Para extender un elemento, deberás crear una definición de clase que herede de la interfaz DOM correcta. Por ejemplo, un elemento personalizado que extiende <button>
debe heredar de HTMLButtonElement
en lugar de HTMLElement
.
Del mismo modo, un elemento que extiende <img>
debe extender HTMLImageElement
.
Ejemplo: Extensión de <button>
:
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
constructor() {
super(); // always call super() first in the constructor.
this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', (e) => div.remove());
}
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
Observa que la llamada a define()
cambia ligeramente cuando se extiende un elemento nativo. El tercer parámetro obligatorio le indica al navegador qué etiqueta estás
extendiendo. Esto es necesario porque muchas etiquetas HTML comparten la misma interfaz de DOM. <section>
, <address>
y <em>
(entre otros) comparten HTMLElement
; <q>
y <blockquote>
comparten HTMLQuoteElement
, etcétera. Especificar {extends: 'blockquote'}
le permite al navegador saber que estás creando un <blockquote>
mejorado en lugar de un <q>
. Consulta la especificación de HTML para obtener la lista completa de las interfaces de DOM de HTML.
Los consumidores de un elemento integrado personalizado pueden usarlo de varias maneras. Para declararlo, agrega el atributo is=""
a la etiqueta nativa:
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
crea una instancia en JavaScript:
// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);
o bien usa el operador new
:
let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;
Este es otro ejemplo que extiende <img>
.
Ejemplo: Extiende <img>
:
customElements.define('bigger-img', class extends Image {
// Give img default size if users don't specify.
constructor(width=50, height=50) {
super(width * 10, height * 10);
}
}, {extends: 'img'});
Los usuarios declaran este componente de la siguiente manera:
<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">
o crea una instancia en JavaScript:
const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);
Detalles diversos
Elementos desconocidos en comparación con elementos personalizados no definidos
El HTML es flexible y tolerante. Por ejemplo, declara <randomtagthatdoesntexist>
en una página y el navegador la aceptará sin problemas. ¿Por qué funcionan las etiquetas no estándar? La respuesta es que la especificación HTML lo permite. Los elementos que no se definen en la especificación se analizan como HTMLUnknownElement
.
No ocurre lo mismo con los elementos personalizados. Los posibles elementos personalizados se analizan como un HTMLElement
si se crean con un nombre válido (incluye un “-”). Puedes verificar esto en un navegador que admita elementos personalizados. Inicia la consola:
Ctrl+Mayúsculas+J (o Cmd+Opción+J en Mac) y pega las
siguientes líneas de código:
// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true
// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true
Referencia de la API
El elemento global customElements
define métodos útiles para trabajar con elementos personalizados.
define(tagName, constructor, options)
Define un nuevo elemento personalizado en el navegador.
Ejemplo
customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});
get(tagName)
Dado un nombre de etiqueta de elemento personalizado válido, muestra el constructor del elemento.
Muestra undefined
si no se registró ninguna definición de elemento.
Ejemplo
let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();
whenDefined(tagName)
Muestra una promesa que se resuelve cuando se define el elemento personalizado. Si el elemento ya está definido, resolvénlo de inmediato. Rechaza si el nombre de la etiqueta no es un nombre de elemento personalizado válido.
Ejemplo
customElements.whenDefined('app-drawer').then(() => {
console.log('ready!');
});
Historial y compatibilidad con navegadores
Si has estado siguiendo los componentes web durante los últimos años, sabrás que Chrome 36 y versiones posteriores implementaron una versión de la API de Custom Elements que usa document.registerElement()
en lugar de customElements.define()
. Ahora se considera una versión obsoleta del estándar, llamada v0.
customElements.define()
es la nueva novedad y lo que los proveedores de navegadores están empezando a implementar. Se llama Custom Elements v1.
Si te interesa la especificación v0 anterior, consulta el artículo de html5rocks.
Navegadores compatibles
Chrome 54 (estado), Safari 10.1 (estado) y Firefox 63 (estado) tienen Elementos personalizados v1. Edge comenzó su desarrollo.
Para detectar elementos personalizados, verifica la existencia de window.customElements
:
const supportsCustomElementsV1 = 'customElements' in window;
Polyfill
Hasta que la compatibilidad con navegadores esté disponible de forma general, hay un polyfill independiente disponible para Custom Elements v1. Sin embargo, te recomendamos que uses el cargador webcomponents.js para cargar de forma óptima los polyfills de componentes web. El cargador usa la detección de funciones para cargar de forma asíncrona solo los rellenos de polimorfismo necesarios que requiere el navegador.
Instálalo:
npm install --save @webcomponents/webcomponentsjs
Uso:
<!-- Use the custom element on the page. -->
<my-element></my-element>
<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>
<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
WebComponents.waitFor(() => {
// At this point we are guaranteed that all required polyfills have
// loaded, and can use web components APIs.
// Next, load element definitions that call `customElements.define`.
// Note: returning a promise causes the custom elements
// polyfill to wait until all definitions are loaded and then upgrade
// the document in one batch, for better performance.
return loadScript('my-element.js');
});
</script>
Conclusión
Los elementos personalizados nos brindan una nueva herramienta para definir etiquetas HTML nuevas en el navegador y crear componentes reutilizables. Combínalos con las otras primitivas de la plataforma nuevas, como Shadow DOM y <template>
, y comenzamos a darnos cuenta del panorama general de los componentes web:
- Multinavegador (estándar web) para crear y extender componentes reutilizables.
- No requiere bibliotecas ni frameworks para comenzar a usarlo. ¡Vanilla JS/HTML por la victoria!
- Proporciona un modelo de programación familiar. Es solo DOM/CSS/HTML.
- Funciona bien con otras funciones nuevas de la plataforma web (Shadow DOM,
<template>
, propiedades personalizadas de CSS, etcétera). - Está estrechamente integrada con las herramientas para desarrolladores del navegador.
- Aprovecha las funciones de accesibilidad existentes.