Elementos personalizados v1: Componentes web reutilizables

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 nuevas etiquetas HTML, refuerce las etiquetas HTML existentes o extienda los componentes de otros desarrolladores. creado. La API es la base de la Web componentes. Ofrece una experiencia basada en estándares para crear componentes reutilizables usando solo JS/HTML/CSS estándar. El resultado es menos código, modular el código y más reutilización nuestras aplicaciones.

Introducción

El navegador nos proporciona una herramienta excelente para estructurar aplicaciones web. Es llamada HTML. Quizás hayas oído hablar de él. Es declarativo, portátil, y fácil de usar. Como puede ser el lenguaje HTML, su vocabulario y la extensibilidad son limitados. La vivencia HTML estándar nunca tuvo una forma de asociar automáticamente el comportamiento de JS con tu lenguaje de marcado... hasta ahora.

Los elementos personalizados son la respuesta a la modernización de HTML, completando el espacio piezas y agrupar la estructura con el comportamiento. Si HTML no proporciona la a un problema, podemos crear un elemento personalizado que lo haga. Personalizado le enseñan nuevos trucos al navegador y, a la vez, preservan los beneficios del HTML.

Cómo definir un elemento nuevo

Para definir un nuevo elemento HTML, necesitamos la potencia de JavaScript.

El global customElements se usa para definir un elemento personalizado y enseñar. al navegador sobre una nueva etiqueta. Llama a customElements.define() con el nombre de la etiqueta. que deseas crear y un class de JavaScript que extienda la HTMLElement base.

Ejemplo: se define un 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 el uso de un elemento personalizado no es diferente del con un <div> o cualquier otro elemento. Las instancias se pueden declarar en la página, de forma dinámica en JavaScript, adjuntar objetos de escucha de eventos, etcétera. para ver más ejemplos.

Define la API de JavaScript de un elemento

La funcionalidad de un elemento personalizado se define con un ES2015. class que extiende HTMLElement. Extender HTMLElement garantiza que el elemento personalizado hereda toda la API del DOM y se refiere a todas las propiedades o métodos que agregues al forman parte de la interfaz del DOM del elemento. Básicamente, usa la clase para crear una API pública de JavaScript para tu etiqueta.

Ejemplo: Definición de la interfaz del 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, disabled y un método toggleDrawer(). También refleja propiedades como HTML atributos.

Una característica interesante de los elementos personalizados es que this dentro de una definición de clase hace referencia al elemento DOM en sí, es decir, la instancia de la clase. En nuestra Por ejemplo, this hace referencia a <app-drawer>. Así ((...)) es como el elemento puede adjunta un objeto de escucha click a sí mismo. Además, no estarás limitado a los objetos de escucha de eventos. Toda la API del DOM está disponible dentro del código del elemento. Usa this para acceder a la propiedades del elemento, inspeccionar sus elementos secundarios (this.children), consultar nodos (this.querySelectorAll('.items')), etcétera

Reglas sobre la creación de elementos personalizados

  1. El nombre de un elemento personalizado debe contener un guion (-). Es decir, <x-tags>, <my-element> y <my-awesome-app> son nombres válidos, mientras que <tabs> y <foo_bar> no. Este requisito es para que el analizador de HTML pueda a distinguir entre los elementos personalizados y los normales. También garantiza que los usuarios compatibilidad cuando se agregan etiquetas nuevas a HTML.
  2. No puedes registrar la misma etiqueta más de una vez. Si intentas hacerlo, arroja una DOMException. Una vez que informaste al navegador sobre una nueva etiqueta, que la modifica. Sin devoluciones.
  3. Los elementos personalizados no pueden cerrarse automáticamente porque HTML solo permite unos pocos elementos se cierren por sí solos. Escribir siempre 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 interesantes de su existencia. Estos se denominan elementos personalizados reacciones.

Nombre Se llama cuando
constructor Una instancia del elemento es crean o actualizan. Útil para inicializar el estado, la configuración de objetos de escucha de eventos crear un shadow DOM Consulta la spec para conocer las restricciones sobre lo que puedes hacer en constructor.
connectedCallback Se llama cada vez que el se inserta en el DOM. Son útiles para ejecutar código de configuración, como recuperación de recursos o renderización. En general, debes intentar retrasar el trabajo hasta ahora.
disconnectedCallback Se llama cada vez que se quita el elemento del DOM. Útil para ejecutando el código de limpieza.
attributeChangedCallback(attrName, oldVal, newVal) Se llama cuando se ha observado un atributo observado. agregar, quitar, actualizar o reemplazar. También se llama para valores iniciales Cuando el analizador crea un elemento. actualizado. Nota: Solo atributos enumerados en la propiedad observedAttributes para recibir esta devolución de llamada.
adoptedCallback El se trasladó el elemento personalizado a un document nuevo (p.ej., alguien 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á de inmediato a attributeChangedCallback(). Del mismo modo, recibirás un disconnectedCallback() justo después de que tu elemento esté listo. Se quita del DOM (p.ej., el usuario llama a el.remove()).

Ejemplo: Se agregaron 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 cuando tengan sentido. Si tu elemento es lo suficientemente complejo y abre una conexión con IndexedDB en connectedCallback(), haz lo necesario trabajo de limpieza en disconnectedCallback(). Pero ten cuidado. No puedes confiar en tu el elemento completamente 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 los valores de hidden o id se cambian en JS:

div.id = 'my-id';
div.hidden = true;

Los valores se aplican al DOM activo como atributos:

<div id="my-id" hidden>

Esto se denomina "propiedades de reflexión para 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 la accesibilidad y CSS los selectores dependen de los atributos para funcionar.

Reflejar una propiedad es útil en cualquier lugar en el que desees conservar el DOM del elemento representada en sincronización con su estado de JavaScript. Una razón por la que quizás quieras reflejan una propiedad es para que se aplique un estilo definido por el usuario cuando cambie el estado de JS.

Recuerda nuestro <app-drawer>. Es posible que un usuario de este componente quiera fundirlo. o impedir 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 sea agregar al DOM para que coincida el selector del usuario. El elemento puede proporcionar que 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();
}

Observar 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 para cada cambio a los atributos enumerados 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, se establecen atributos adicionales en el objeto <app-drawer> cuando un elemento Se cambió el atributo disabled. Aunque no lo estamos haciendo aquí, podría también usar la attributeChangedCallback para mantener una propiedad JS sincronizada con su .

Actualizaciones de elementos

HTML progresivamente mejorado

Ya aprendimos que los elementos personalizados se definen llamando customElements.define() Pero esto no significa que deba definir y registrar un un elemento personalizado todo a la vez.

Los elementos personalizados se pueden usar antes de registrar su definición.

La mejora progresiva es una característica de los elementos personalizados. En otras palabras, puedes declarar varios 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 el navegador da a los posibles elementos personalizados un tratamiento diferente al de los elementos etiquetas. El proceso de llamar a define() y otorgar un permiso existente con una definición de clase se llama “actualizaciones de elementos”.

Para saber cuándo se define el nombre de una etiqueta, puedes usar window.customElements.whenDefined() Devuelve 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 el elemento

Los elementos personalizados pueden administrar su propio contenido mediante las APIs del DOM dentro el 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>";
  }
  // ...
});

La declaración de esta etiqueta producirá lo siguiente:

<x-foo-with-markup>
  <b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>

// TODO: DevSite - Se quitó la muestra de código porque usaba controladores de eventos intercalados

Cómo crear un elemento que use Shadow DOM

Shadow DOM ofrece a un elemento una forma de poseer, representar y aplicar diseño a una parte de un DOM independiente del resto de la página. ¡Diablos! Incluso podrías esconder Toda la app en 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ó la muestra de código porque usaba controladores de eventos intercalados

Cómo crear elementos a partir de un <template>

Para quienes no conocen, la <template> elemento te permite declarar fragmentos del DOM que se analizan, están inertes al cargar la página y se puede activar más adelante en el entorno de ejecución. Es otra primitiva de API en la Web de la familia de componentes. Las plantillas son marcadores de posición ideales para declarar el de un elemento personalizado.

Ejemplo: registrar 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 &lt;template&gt;.</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 tienen una gran capacidad. Veamos los puntos clave el:

  1. Definiremos un nuevo elemento en HTML: <x-foo-from-template>
  2. El Shadow DOM del elemento se crea a partir de un <template>
  3. El DOM del elemento es local del elemento gracias al Shadow DOM.
  4. La CSS interna del elemento se define en función del elemento gracias al Shadow DOM.

Estoy en Shadow DOM. Mi marca se selló a partir de una <template>.

// TODO: DevSite - Se quitó la muestra de código porque usaba controladores de eventos intercalados

Cómo aplicar diseño a un elemento personalizado

Incluso si tu elemento define su propio estilo usando Shadow DOM, los usuarios pueden aplicar ajustes de diseño 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, prevalecen los estilos del usuario. Ellos anulan siempre el diseño definido por el elemento. Consulta la sección Cómo crear un elemento que usa Shadow DOM.

Cómo aplicar diseño previo a elementos no registrados

Antes de que un elemento se actualice, puede orientarse a él en CSS mediante el Pseudoclase :defined. Esto es útil para aplicar estilo previo a un componente. Para ejemplo, puedes evitar el diseño u otro FOUC visual ocultando las imágenes de los componentes y atenuarlos cuando se definan.

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 nuevos elementos HTML, pero también útil para extender otros elementos personalizados o incluso el HTML integrado del navegador.

Extiende un elemento personalizado

La extensión de otro elemento personalizado se realiza extendiendo su definición de clase.

Ejemplo: crea <fancy-app-drawer> que extienda <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 deseas crear un <button> más atractivo. En lugar de replicar el y la funcionalidad de <button>, una mejor opción es mejoran los elementos existentes con elementos personalizados.

Un elemento integrado personalizado es aquel que extiende uno de los las etiquetas HTML integradas del navegador. El beneficio principal de ampliar un es obtener todas sus funciones (propiedades del DOM, métodos, accesibilidad). No hay mejor forma de escribir una Web progresiva que mejorar progresivamente el código HTML existente de los elementos de una sola vez.

Para extender un elemento, deberás crear una definición de clase que herede de la interfaz del DOM correcta. Por ejemplo, un elemento personalizado que extiende <button> debe heredar contenido de HTMLButtonElement en lugar de HTMLElement. De manera similar, un elemento que extiende <img> debe extender HTMLImageElement.

Ejemplo, extendiendo <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 código nativo. . El tercer parámetro obligatorio le indica al navegador qué etiqueta estás sin extender. Esto es necesario porque muchas etiquetas HTML comparten el mismo DOM. interfaz de usuario. <section>, <address> y <em> (entre otros) comparten HTMLElement; tanto <q> como <blockquote> comparten HTMLQuoteElement; Y así sucesivamente. Si especificas {extends: 'blockquote'}, se indica al navegador que estás creando un se incendió <blockquote> en lugar de <q>. Consulta el archivo HTML spec para acceder a la lista completa de interfaces de DOM de HTML.

Los consumidores de un elemento integrado personalizado pueden usarlo de varias maneras. Pueden Para declararlo, agrega el atributo is="" en 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);

También puedes usar el operador new:

let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;

Aquí hay otro ejemplo que extiende <img>.

Ejemplo, extendiendo <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);

Otros detalles

Elementos desconocidos frente a elementos personalizados sin definir

El lenguaje HTML es flexible y flexible para trabajar. Por ejemplo, puedes declarar <randomtagthatdoesntexist> en una página y el navegador está perfectamente satisfecho. aceptarlo. ¿Por qué funcionan las etiquetas no estándar? La respuesta es: especificación lo permite. Los elementos que la especificación no define se analizan como HTMLUnknownElement

No ocurre lo mismo con los elementos personalizados. Se analizan los posibles elementos personalizados. como HTMLElement si se crearon con un nombre válido (incluye un “-”). Tú puedes verificar esto en un navegador compatible con elementos personalizados. Inicia la consola: Ctrl + Mayúsculas + J (o Cmd + Opt + J en Mac) y pega el 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 global de customElements define métodos útiles para trabajar con conjuntos o de terceros.

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)

Con un nombre de etiqueta de elemento personalizado válido, muestra el constructor del elemento. Muestra undefined si no se registró ninguna definición del 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 botón ya está definido, resuélvelo de inmediato. Se rechaza si el nombre de la etiqueta no es un nombre válido para el elemento personalizado.

Ejemplo

customElements.whenDefined('app-drawer').then(() => {
  console.log('ready!');
});

Historial y compatibilidad del navegador

Si has estado siguiendo los componentes web durante los últimos años, sepa 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(). Ya está considerada una versión obsoleta del estándar, llamada v0. customElements.define() es el nuevo interés y qué son los proveedores de navegadores empezando a implementar. Se llama Custom Elements v1.

Si estás interesado en la especificación anterior v0, echa un vistazo a los html5rocks artículo.

Navegadores compatibles

Chrome 54 (estado), Safari 10.1 (estado) y Firefox 63 (estado) tiene Custom Elements v1 Edge comenzó y desarrollo.

Para detectar elementos personalizados, verifica la existencia de window.customElements

const supportsCustomElementsV1 = 'customElements' in window;

Polyfill

Hasta que la compatibilidad con navegadores esté ampliamente disponible, existe una polyfill independiente disponible para Custom Elements v1. Sin embargo, recomendamos usar webcomponents.js loader para cargar de manera óptima los polyfills de los componentes web. El cargador Usa la detección de funciones para cargar de forma asíncrona solo los rellenos de polyfills 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 proporcionan una nueva herramienta para definir nuevas etiquetas HTML en el navegador y crear componentes reutilizables. Combínalos con la otra plataforma nueva. primitivas, como Shadow DOM y <template>, y empezamos a ver la gran imagen de componentes web:

  • Es compatible con varios navegadores (estándar de la Web) para crear y extender componentes reutilizables.
  • No requiere una biblioteca ni un framework para comenzar. ¡JS/HTML convencionales por la victoria!
  • Proporciona un modelo de programación conocido. Es solo DOM/CSS/HTML.
  • Funciona bien con otras nuevas funciones de plataformas web (Shadow DOM, <template>, CSS). propiedades personalizadas, etc.)
  • Se integra por completo con las Herramientas para desarrolladores del navegador.
  • Aprovecha las funciones de accesibilidad existentes.