Custom Elements
Aprenda a criar Custom Elements em JavaScript: defina uma classe, registre com customElements.define(), use callbacks de ciclo de vida, observe atributos e estenda elementos nativos.
Custom Elements são um dos pilares fundamentais dos Web Components. Eles permitem que você defina suas próprias tags HTML respaldadas por uma classe JavaScript, ampliando o vocabulário nativo do navegador com elementos reutilizáveis e autocontidos que carregam sua própria estrutura, estilo e comportamento.
Esta página cobre tudo o que você precisa para criar um custom element: como defini-lo e registrá-lo, os callbacks de ciclo de vida que o navegador invoca para você, como reagir a mudanças de atributos, como estender elementos nativos e as práticas que mantêm seus componentes robustos e acessíveis.
Dois tipos de custom elements
A especificação define dois formatos, e a distinção afeta como você os cria e utiliza:
- Autonomous custom elements estendem o genérico
HTMLElemente são usados como tags completamente novas:<my-card></my-card>. Este é o caso mais comum. - Customized built-in elements estendem uma classe nativa específica (como
HTMLButtonElement) e são usados com o atributois:<button is="fancy-button">. Eles herdam gratuitamente a acessibilidade e o comportamento do elemento hospedeiro.
Uma regra se aplica a ambos: o nome da tag deve conter um hífen (my-card, e não mycard). O hífen é o que indica ao analisador que a tag é um custom element e evita colisões com futuras tags padrão.
Definindo um Custom Element
Para criar um autonomous custom element, defina uma class que estenda a classe nativa HTMLElement e, em seguida, registre-o no navegador usando customElements.define(tagName, class). A classe encapsula o comportamento do elemento; o registro o conecta a um nome de tag.
Um padrão comum é construir o DOM interno do elemento dentro de um shadow DOM para que sua marcação e estilos fiquem isolados do restante da página.
Exemplo: Criando um Custom Element Simples
<my-custom-element></my-custom-element>
<script>
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<p>Hello, World!</p>`;
}
}
customElements.define('my-custom-element', MyCustomElement);
</script>Este exemplo define um custom element chamado my-custom-element que exibe "Hello, World!" dentro de um shadow DOM. Para usá-lo, basta adicionar <my-custom-element></my-custom-element> em qualquer lugar do seu HTML. Note que custom elements não possuem forma de autofechamento — escreva sempre a tag de fechamento correspondente.
Custom Elements v1 é suportado em todos os navegadores modernos (Chrome 54+, Firefox 52+, Safari 10.1+, Edge 79+). Sempre verifique a compatibilidade com o navegador se precisar dar suporte a ambientes legados.
Callbacks de Ciclo de Vida
Custom elements possuem um conjunto de callbacks de ciclo de vida que permitem aos desenvolvedores executar código em momentos específicos do ciclo de vida do elemento:
connectedCallback(): Invocado cada vez que o custom element é anexado a um elemento conectado ao documento.disconnectedCallback(): Invocado cada vez que o custom element é desconectado do DOM do documento.attributeChangedCallback(name, oldValue, newValue): Invocado cada vez que um dos atributos do custom element é adicionado, removido ou alterado.adoptedCallback(): Invocado cada vez que o custom element é movido para um novo documento.
| Callback | Quando é disparado |
|---|---|
connectedCallback() | O elemento é adicionado ao DOM |
disconnectedCallback() | O elemento é removido do DOM |
attributeChangedCallback(name, oldValue, newValue) | Um atributo observado é alterado |
adoptedCallback() | O elemento é movido para um novo documento |
Um modelo mental útil: o construtor é executado uma vez quando a instância do elemento é criada (antes de estar no DOM, portanto não deve tocar em atributos ou filhos), enquanto connectedCallback pode ser executado várias vezes se o elemento for adicionado, removido e adicionado novamente. Faça a configuração dependente do DOM em connectedCallback e limpe listeners ou temporizadores em disconnectedCallback para evitar vazamentos de memória.
Exemplo: Usando Callbacks de Ciclo de Vida
<lifecycle-element></lifecycle-element>
<script>
class LifecycleElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
#status {
color: blue;
font-weight: bold;
}
</style>
<p>Lifecycle Element</p>
<p id="status">Element not connected</p>
`;
}
connectedCallback() {
this.shadowRoot.getElementById('status').textContent = 'Element connected to the page.';
}
disconnectedCallback() {
this.shadowRoot.getElementById('status').textContent = 'Element disconnected from the page.';
}
}
customElements.define('lifecycle-element', LifecycleElement);
</script>Atributos e Propriedades
Custom elements podem ter atributos e propriedades para gerenciar seu estado e comportamento. Atributos são definidos diretamente no HTML e são sempre strings, enquanto propriedades são definidas no objeto DOM do elemento e podem ser de qualquer tipo de dado.
O detalhe fundamental é o attributeChangedCallback: ele só é disparado para atributos explicitamente listados no getter static get observedAttributes() do elemento. Se um atributo não estiver nesse array, alterá-lo não dispara nenhum callback. Uma convenção comum é expor uma propriedade getter/setter que simplesmente reflete para um atributo, mantendo o código JavaScript e o HTML sincronizados.
Exemplo: Gerenciando Atributos e Propriedades
<attribute-element id="element" data-content="Initial content"></attribute-element>
<button onclick="buttonClicked()">Click to change attribute</button>
<script>
class AttributeElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<p>Attribute Example: <span id="content"></span></p>`;
}
static get observedAttributes() {
return ['data-content'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'data-content') {
this.shadowRoot.getElementById('content').textContent = newValue;
}
}
set content(value) {
this.setAttribute('data-content', value);
}
get content() {
return this.getAttribute('data-content');
}
}
customElements.define('attribute-element', AttributeElement);
function buttonClicked() {
alert('button clicked!');
const ourCustomElement = document.getElementById('element');
ourCustomElement.content = 'New content';
}
</script>Aqui, o attribute-element atualiza seu conteúdo com base no atributo data-content. A propriedade content fornece uma forma conveniente de obter e definir esse atributo de forma programática.
Estendendo Elementos Nativos
Customized built-in elements estendem uma classe nativa específica e são usados com o atributo is. A grande vantagem é que eles herdam a semântica e a acessibilidade do elemento hospedeiro — um <button is="fancy-button"> ainda é um botão real para o teclado e para leitores de tela.
Exemplo: Estendendo um Elemento Nativo
<button is="fancy-button">Click me!</button>
<script>
class FancyButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', () => {
alert('Fancy button clicked!');
});
}
}
customElements.define('fancy-button', FancyButton, { extends: 'button' });
</script>Aqui, fancy-button estende o elemento padrão <button>, adicionando uma mensagem de alerta quando o botão é clicado. O terceiro argumento de customElements.define — { extends: 'button' } — informa ao navegador a qual tag este elemento personalizado se aplica.
O Safari não suporta customized built-in elements (a forma is=). Para maior compatibilidade, prefira autonomous custom elements e reimplemente a acessibilidade necessária, ou carregue um polyfill.
Boas Práticas para Custom Elements
- Use Shadow DOM: Sempre encapsule a estrutura interna e os estilos do seu custom element usando o Shadow DOM.
- Defina APIs Claras: Forneça APIs claras e intuitivas para seus custom elements por meio de atributos e propriedades bem documentados.
- Gerenciamento do Ciclo de Vida: Gerencie adequadamente os callbacks de ciclo de vida do elemento para garantir um comportamento robusto e evitar vazamentos de memória.
- Acessibilidade: Certifique-se de que seus custom elements sejam acessíveis, incluindo funções e propriedades ARIA adequadas.
- Testes: Teste minuciosamente seus custom elements em diferentes navegadores e ambientes para garantir compatibilidade e estabilidade.
Conclusão
Custom elements oferecem uma maneira poderosa de estender o HTML, permitindo a criação de componentes reutilizáveis e encapsulados com comportamento personalizado. Ao aproveitar os recursos dos custom elements — incluindo callbacks de ciclo de vida, atributos, propriedades e o Shadow DOM — os desenvolvedores podem construir aplicações web sofisticadas e de fácil manutenção.
Comece a experimentar com custom elements em seus projetos hoje mesmo e desbloqueie novas possibilidades para o desenvolvimento web. Os exemplos fornecidos aqui são apenas o começo — use-os como base para criar seus próprios custom elements inovadores.
Tópicos relacionados
- Web Components — o padrão abrangente ao qual os custom elements pertencem.
- Shadow DOM — encapsule o DOM interno e os estilos de um elemento.
- Shadow DOM Styling — estilize o interior do seu componente.
- Shadow DOM Slots and Composition — permita que os usuários projetem conteúdo no seu elemento.
- The
<template>Element — clone marcação de forma eficiente para o shadow root do seu elemento. - Class Basic Syntax — os recursos de classe sobre os quais os custom elements são construídos.