Os elementos personalizados permitem que desenvolvedores da Web definam novas tags HTML, ampliem as já existentes e criem componentes da Web reutilizáveis.
Com os elementos personalizados, os desenvolvedores da Web podem criar novas tags HTML, aprimorar as tags HTML existentes ou ampliar os componentes criados por outros desenvolvedores. A API é a base dos componentes da Web. Ela disponibiliza uma forma baseada em padrões da Web para criar componentes reutilizáveis usando nada mais que JS/HTML/CSS comuns. O resultado é menos código, código modular e mais reutilização nos apps.
Introdução
O navegador nos oferece uma excelente ferramenta para estruturar aplicativos da web. Ele se chama HTML. Talvez você já tenha ouvido falar dele. O HTML é declarativo, portátil, tem amplo suporte e é fácil de trabalhar. O HTML é ótimo. No entanto, seu vocabulário e capacidade de extensão são limitados. O padrão atual do HTML sempre teve uma forma de associar automaticamente o comportamento do JS à marcação... até agora.
Os elementos personalizados são a resposta para a modernização do HTML, preenchendo as partes ausentes e agrupando estrutura e comportamento. Se o HTML não fornecer a solução para um problema, podemos criar um elemento personalizado que faça isso. Os elementos personalizados ensinam novas funcionalidades ao navegador, sem deixar de preservar os benefícios do HTML.
Definição de um novo elemento
Para definir um novo elemento do HTML, precisamos dos recursos do JavaScript.
O objeto global customElements
é usado para definir um elemento personalizado e ensinar o navegador sobre uma nova tag. Chame customElements.define()
com o nome da tag
que você quer criar e um class
do JavaScript que estende a HTMLElement
de base.
Exemplo: definição de um painel de gaveta para dispositivos móveis, <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 {...});
Exemplo de uso:
<app-drawer></app-drawer>
É importante lembrar que usar um elemento personalizado não é diferente de
usar um <div>
ou qualquer outro elemento. As instâncias podem ser declaradas na página,
criadas dinamicamente no JavaScript, é possível anexar ouvintes de eventos etc. Continue
lendo para conferir mais exemplos.
Definir a API JavaScript de um elemento
A funcionalidade de um elemento personalizado é definida usando um ES2015
class
que estende HTMLElement
. A extensão de HTMLElement
garante que o elemento personalizado
herde toda a API DOM e significa que todas as propriedades/métodos adicionados à
classe passam a fazer parte da interface DOM do elemento. Basicamente, use a classe para criar uma API JavaScript pública para sua tag.
Exemplo: definição da interface 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);
Neste exemplo, criamos uma gaveta que tem uma propriedade open
, uma propriedade disabled
e um método toggleDrawer()
. Ele também reflete propriedades como atributos
do HTML.
Um recurso elegante dos elementos personalizados é que this
dentro de uma definição de classe
se refere ao próprio elemento DOM, ou seja, à instância da classe. No nosso
exemplo, this
se refere a <app-drawer>
. É assim (bio mola) que o elemento pode
anexar um listener click
a si mesmo. E você não está limitado a listeners de eventos.
Toda a API DOM está disponível dentro do código do elemento. Use this
para acessar as
propriedades do elemento, inspecionar os filhos (this.children
), consultar nós
(this.querySelectorAll('.items')
) etc.
Regras para a criação de elementos personalizados
- O nome de um elemento personalizado precisa conter um hífen (-). Portanto,
<x-tags>
,<my-element>
e<my-awesome-app>
são nomes válidos, enquanto<tabs>
e<foo_bar>
não são. O motivo desse requisito é que o analisador HTML possa distinguir elementos personalizados de elementos normais. Ele também garante compatibilidade com encaminhamento quando novas tags são adicionadas ao HTML. - Não é possível registrar a mesma tag mais de uma vez. Se você tentar fazer isso,
uma
DOMException
será gerada. Informe o navegador sobre uma nova tag. É só isso. Não é possível cancelar. - Os elementos personalizados não podem se fechar automaticamente, porque o HTML permite que alguns
elementos
se fechem automaticamente. Sempre escreva uma tag de fechamento
(
<app-drawer></app-drawer>
).
Reações de elementos personalizados
Um elemento personalizado pode definir hooks de ciclo de vida especiais para executar o código durante momentos interessantes da existência dele. Essas são as reações do elemento personalizado.
Nome | Chamado quando |
---|---|
constructor |
Uma instância do elemento é
criada ou atualizada. Útil para inicializar
um estado, configurar listeners de eventos ou
criar um shadow DOM.
Consulte a
especificação
para ver as restrições sobre o que é possível fazer no constructor .
|
connectedCallback |
É chamado sempre que o elemento é inserido no DOM. Útil para executar código de configuração, como recuperar recursos ou renderizar. Geralmente, tente atrasar o trabalho até esse momento. |
disconnectedCallback |
Chamada sempre que o elemento é removido do DOM. Útil para executar o código de limpeza. |
attributeChangedCallback(attrName, oldVal, newVal) |
Chamado quando um atributo observado é
adicionado, removido, atualizado ou substituído. Também chamado para valores iniciais
quando um elemento é criado pelo analisador ou
atualizado. Observação: apenas
atributos listados na propriedade observedAttributes vão
receber esse callback.
|
adoptedCallback |
O
elemento personalizado foi movido para um novo document (por exemplo,
alguém chamado document.adoptNode(el) ).
|
Os callbacks de reação são síncronos. Se alguém chamar el.setAttribute()
no elemento, o navegador vai chamar attributeChangedCallback()
imediatamente.
Da mesma forma, você vai receber um disconnectedCallback()
logo após o elemento ser
removido do DOM (por exemplo, o usuário chama el.remove()
).
Exemplo: adicionar reações de elemento personalizado a <app-drawer>
:
class AppDrawer extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// ...
}
connectedCallback() {
// ...
}
disconnectedCallback() {
// ...
}
attributeChangedCallback(attrName, oldVal, newVal) {
// ...
}
}
Defina as reações se/quando fizer sentido. Se o elemento for bastante complexo
e abrir uma conexão ao IndexedDB em connectedCallback()
, faça o trabalho de limpeza
necessário em disconnectedCallback()
. Mas tome cuidado. Não é possível depender do elemento sendo removido do DOM em todas as circunstâncias. Por exemplo,
disconnectedCallback()
nunca será chamado se o usuário fechar a guia.
Propriedades e atributos
Como refletir propriedades em atributos
É comum que as propriedades do HTML reflitam seu valor no DOM como um
atributo HTML. Por exemplo, quando os valores de hidden
ou id
são alterados no JS:
div.id = 'my-id';
div.hidden = true;
Os valores são aplicados ao DOM em execução como atributos:
<div id="my-id" hidden>
Isso é chamado de "refletir propriedades para atributos". Praticamente todas as propriedades do HTML fazem isso. Por quê? Os atributos também são úteis para configurar um elemento de forma declarativa, e algumas APIs, como acessibilidade e seletores CSS, dependem desses atributos para funcionar.
A reflexão de uma propriedade é útil sempre que você quiser manter a representação do elemento no DOM em sincronia com o estado do JavaScript. Um motivo para refletir uma propriedade é aplicar estilos definidos pelo usuário quando o estado do JS muda.
Lembre-se de nossa <app-drawer>
. Um consumidor desse componente pode querer esmaecê-lo
e/ou evitar interações do usuário enquanto o componente estiver desativado:
app-drawer[disabled] {
opacity: 0.5;
pointer-events: none;
}
Quando a propriedade disabled
é alterada no JS, queremos que esse atributo seja
adicionado ao DOM para que o seletor do usuário corresponda. O elemento pode fornecer esse
comportamento refletindo o valor para um atributo do mesmo nome:
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 mudanças nos atributos
Os atributos HTML são uma maneira conveniente para os usuários declararem o estado inicial:
<app-drawer open disabled></app-drawer>
Os elementos podem reagir às mudanças de atributos definindo um
attributeChangedCallback
. O navegador vai chamar esse método para cada mudança
nos atributos listados na matriz 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.
}
}
No exemplo, estamos definindo atributos adicionais no <app-drawer>
quando um
atributo disabled
é alterado. Embora não façamos isso aqui, você também pode
usar o attributeChangedCallback
para manter uma propriedade do JS sincronizada com o
atributo.
Upgrades de elementos
HTML aprimorado progressivamente
Já aprendemos que elementos personalizados são definidos chamando
customElements.define()
. Mas isso não significa que você tem de definir e registrar um
elemento personalizado de uma só vez.
Os elementos personalizados podem ser usados antes do registro da definição.
O aprimoramento progressivo é um recurso dos elementos personalizados. Em outras palavras, você pode
declarar vários elementos <app-drawer>
na página e só invocar
customElements.define('app-drawer', ...)
muito depois. Isso acontece porque o
navegador trata possíveis elementos personalizados de forma diferente graças a tags
desconhecidas. O processo de chamar define()
e aprimorar um elemento
existente com uma definição de classe é chamado de "atualizações de elementos".
Para saber quando um nome de tag é definido, use
window.customElements.whenDefined()
. Ele retorna uma promessa que é resolvida quando o
elemento é definido.
customElements.whenDefined('app-drawer').then(() => {
console.log('app-drawer defined');
});
Exemplo: atrasar o trabalho até que um conjunto de elementos filhos seja atualizado
<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.
});
Conteúdo definido pelo elemento
Os elementos personalizados podem gerenciar o próprio conteúdo usando as APIs DOM dentro do código do elemento. As reações são úteis para isso.
Exemplo: crie um elemento com um HTML padrão:
customElements.define('x-foo-with-markup', class extends HTMLElement {
connectedCallback() {
this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
}
// ...
});
A declaração dessa tag vai gerar:
<x-foo-with-markup>
<b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>
// TODO: DevSite - O exemplo de código foi removido porque usava manipuladores de eventos inline.
Como criar um elemento que usa o Shadow DOM
O Shadow DOM permite que um elemento tenha, renderize e estilize um bloco de DOM separado do resto da página. Você pode até ocultar todo um app em uma única tag:
<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>
Para usar o Shadow DOM em um elemento personalizado, chame this.attachShadow
dentro do
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));
}
// ...
});
Exemplo 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 do usuário
// TODO: DevSite - O exemplo de código foi removido porque usava manipuladores de eventos inline.
Como criar elementos usando uma <template>
Para quem não conhece, o elemento
<template>
permite declarar fragmentos do DOM que são analisados, ficam inertes na carga da página e
podem ser ativados posteriormente em tempo de execução. É outro primitivo de API na família de componentes da Web. Os modelos são um marcador de posição ideal para declarar a
estrutura de um elemento personalizado.
Exemplo: registrar um elemento com conteúdo do Shadow DOM criado usando um
<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>
Essas poucas linhas de código fazem muita coisa. Vamos entender as principais atividades em andamento:
- Estamos definindo um novo elemento no HTML:
<x-foo-from-template>
- O Shadow DOM do elemento é criado usando uma
<template>
- O DOM do elemento é local em relação ao elemento, graças ao Shadow DOM
- O CSS interno do elemento tem o escopo do elemento, graças ao Shadow DOM
Estou no Shadow DOM. Minha marcação foi copiada de um <template>.
// TODO: DevSite - Exemplo de código removido porque usava manipuladores de eventos in-line
Aplicar um estilo a um elemento personalizado
Mesmo que o elemento defina seu próprio estilo usando o Shadow DOM, os usuários podem estilizar o elemento personalizado na página. Eles são chamados de "estilos definidos pelo usuário".
<!-- 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>
Talvez você esteja se perguntando como funciona a especificidade do CSS se o elemento tiver estilos definidos no Shadow DOM. Em termos de especificidade, os estilos do usuário predominam. Eles sempre substituem o estilo definido pelo elemento. Consulte a seção sobre Como criar um elemento que usa o Shadow DOM.
Aplicar estilo predefinido em elementos não registrados
Antes que um elemento seja atualizado, você pode fazer referência a ele no CSS usando a
pseudoclasse :defined
. Isso é útil para aplicar estilo predefinido a um componente. Por
exemplo, para evitar layout ou outro flash de conteúdo sem estilo (FOUC) visual, oculte componentes
indefinidos e exiba-os quando forem definidos.
Exemplo: oculte <app-drawer>
antes de ser definido:
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;
}
Depois que <app-drawer>
for definido, o seletor (app-drawer:not(:defined)
)
não vai mais corresponder.
Como estender elementos
A API Custom Elements é útil para criar novos elementos HTML, mas também pode ampliar outros elementos personalizados ou até mesmo o HTML integrado no navegador.
Como estender um elemento personalizado
A extensão de outro elemento personalizado é feita estendendo sua definição de classe.
Exemplo: crie <fancy-app-drawer>
que estende <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);
Como estender elementos HTML nativos
Digamos que você queira criar uma <button>
mais sofisticada. Em vez de replicar o
comportamento e a funcionalidade de <button>
, uma opção melhor é aprimorar
progressivamente o elemento usando elementos personalizados.
Um elemento incorporado personalizado é um elemento personalizado que estende uma das tags HTML incorporadas do navegador. A principal vantagem da extensão de um elemento existente é ter acesso a todos os recursos dele (propriedades do DOM, métodos, acessibilidade). A melhor maneira de criar um Progressive Web App (link em inglês) é aprimorar progressivamente os elementos HTML existentes.
Para estender um elemento, será necessário criar uma definição de classe que herde
da interface DOM correta. Por exemplo, um elemento personalizado que estende
<button>
precisa herdar de HTMLButtonElement
em vez de HTMLElement
.
Da mesma forma, um elemento que estende <img>
precisa estender HTMLImageElement
.
Exemplo - extensão 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'});
Observe que a chamada a define()
muda um pouco ao estender um elemento
nativo. O terceiro parâmetro obrigatório informa ao navegador qual tag você está estendendo. Isso é necessário porque muitas tags HTML compartilham a mesma interface DOM. <section>
, <address>
e <em>
(entre outros) compartilham
HTMLElement
; <q>
e <blockquote>
compartilham HTMLQuoteElement
; etc.
Especificar {extends: 'blockquote'}
informa ao navegador que você está criando uma
<blockquote>
aprimorada em vez de uma <q>
. Consulte a especificação
do HTML
para conferir a lista completa de interfaces do DOM do HTML.
Os clientes de um elemento incorporado personalizado podem usá-lo de várias maneiras. Ele pode
declará-la adicionando o atributo is=""
à tag nativa:
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
crie uma instância em 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);
ou use o operador new
:
let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;
Veja outro exemplo que estende <img>
.
Exemplo: como estender <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'});
Os usuários declaram esse componente como:
<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">
ou criam uma instância no 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);
Detalhes diversos
Elementos desconhecidos x elementos personalizados indefinidos
O HTML pode ser usado de forma leniente e flexível. Por exemplo, declare
<randomtagthatdoesntexist>
em uma página e o navegador vai aceitar
isso sem problemas. Por que as tags não padrão funcionam? A resposta é que a especificação
do HTML
permite isso. Elementos não definidos na especificação são analisados como
HTMLUnknownElement
.
O mesmo não acontece com elementos personalizados. Os possíveis elementos personalizados são analisados
como HTMLElement
se criados com um nome válido (incluindo um "-"). Você
pode verificar isso em um navegador que ofereça suporte a elementos personalizados. Ative o console:
Ctrl+Shift+J (ou Cmd+Opt+J no Mac) e cole as
seguintes linhas 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
Referência da API
O customElements
global define métodos úteis para trabalhar com elementos
personalizados.
define(tagName, constructor, options)
Define um novo elemento personalizado no navegador.
Exemplo
customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});
get(tagName)
Dado um nome de tag de elemento personalizado válido, retorna o construtor do elemento.
Retornará undefined
se nenhuma definição de elemento tiver sido registrada.
Exemplo
let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();
whenDefined(tagName)
Retorna uma promessa que será resolvida quando o elemento personalizado for definido. Se o elemento já estiver definido, resolva imediatamente. Rejeita se o nome da tag não for um nome de elemento personalizado válido.
Exemplo
customElements.whenDefined('app-drawer').then(() => {
console.log('ready!');
});
Histórico e suporte a navegadores
Se você acompanhou os componentes da Web nos últimos dois anos, sabe
que o Chrome 36+ implementou uma versão da API Custom Elements que usa
document.registerElement()
em vez de customElements.define()
. Ela agora
é considerada uma versão descontinuada do padrão, chamada v0.
customElements.define()
é o mais recente e quais fornecedores de navegadores estão começando a implementar. É a versão Custom Elements v1.
Se você ainda estiver interessado na especificação v0 antiga, confira o artigo de html5rocks.
Suporte ao navegador
O Chrome 54 (status), o Safari 10.1 (status) e o Firefox 63 (status) têm Custom Elements v1. O Edge iniciou o desenvolvimento.
Para detectar elementos personalizados, verifique a existência de
window.customElements
:
const supportsCustomElementsV1 = 'customElements' in window;
Polyfill
Até que o suporte a navegadores esteja amplamente disponível, há um polyfill independente disponível para os Custom Elements v1. No entanto, recomendamos usar o webcomponents.js loader para carregar da melhor forma possível os polyfills de componentes da Web. O loader usa a detecção de recursos para carregar de forma assíncrona apenas os pollyfills necessários requeridos pelo navegador.
Instale:
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>
Conclusão
Os elementos personalizados nos oferecem uma nova ferramenta para definir novas tags HTML no navegador e
criar componentes reutilizáveis. Combine-os com outros novos primitivos da plataforma,
como Shadow DOM e <template>
, para começar a entender o panorama
geral dos Web Components:
- Compatível com todos os navegadores (padrão da Web) para criar e estender componentes reutilizáveis.
- Não exige biblioteca ou framework para começar a usar. JS/HTML comum... fantástico!
- Oferece um modelo de programação conhecido. É apenas DOM/CSS/HTML.
- Funciona bem com outros novos recursos da plataforma da Web (Shadow DOM,
<template>
, propriedades personalizadas do CSS etc.) - Estreitamente integrado com o DevTools do navegador.
- Use os recursos de acessibilidade atuais.