Web Components
Aula 4 de 6
Web Components
Web Components são um conjunto de APIs nativas do navegador para criar componentes reutilizáveis e encapsulados.
Custom Elements
class MeuBotao extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['tipo', 'label'];
}
connectedCallback() {
this.render();
this.addEventListener('click', this._handleClick);
}
disconnectedCallback() {
this.removeEventListener('click', this._handleClick);
}
attributeChangedCallback(nome, oldVal, newVal) {
if (oldVal !== newVal) this.render();
}
_handleClick() {
this.dispatchEvent(new CustomEvent('meu-click', {
detail: { tipo: this.getAttribute('tipo') },
bubbles: true
}));
}
render() {
const label = this.getAttribute('label') || 'Clique';
const tipo = this.getAttribute('tipo') || 'primario';
this.shadowRoot.innerHTML = `
<style>
button {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: transform 0.2s;
}
button:hover { transform: scale(1.05); }
.primario { background: #3498db; color: white; }
.perigo { background: #e74c3c; color: white; }
</style>
<button class="${tipo}">${label}</button>
`;
}
}
customElements.define('meu-botao', MeuBotao);
<meu-botao tipo="primario" label="Salvar"></meu-botao>
<meu-botao tipo="perigo" label="Excluir"></meu-botao>
<script>
document.querySelector('meu-botao').addEventListener('meu-click', (e) => {
console.log('Botão clicado:', e.detail.tipo);
});
</script>
Shadow DOM e Slots
class CardComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ddd;
border-radius: 12px;
padding: 1rem;
max-width: 300px;
font-family: system-ui, sans-serif;
}
::slotted(img) {
width: 100%;
border-radius: 8px;
}
.titulo {
font-size: 1.25rem;
font-weight: bold;
margin: 0.5rem 0;
}
</style>
<slot name="imagem"></slot>
<div class="titulo"><slot name="titulo">Título Padrão</slot></div>
<slot></slot>
`;
}
}
customElements.define('meu-card', CardComponent);
<meu-card>
<img slot="imagem" src="foto.jpg" alt="Descrição">
<span slot="titulo">Card Personalizado</span>
<p>Conteúdo do card via slot padrão.</p>
</meu-card>
Templates HTML
<template id="template-lista">
<style>
ul { list-style: none; padding: 0; }
li { padding: 0.5rem; border-bottom: 1px solid #eee; }
li:hover { background: #f5f5f5; }
</style>
<ul id="lista"></ul>
</template>
<div id="container-lista"></div>
class ListaDinamica extends HTMLElement {
constructor() {
super();
const template = document.getElementById('template-lista');
const clone = template.content.cloneNode(true);
this.attachShadow({ mode: 'open' }).appendChild(clone);
}
set items(valores) {
const ul = this.shadowRoot.getElementById('lista');
ul.innerHTML = valores.map(item => `<li>${item}</li>`).join('');
}
}
customElements.define('lista-dinamica', ListaDinamica);
// Uso
const lista = document.querySelector('lista-dinamica');
lista.items = ['HTML', 'CSS', 'JavaScript'];
lit-html Básico
<script type="module">
import { html, render } from 'https://cdn.jsdelivr.net/npm/lit-html@3/+esm';
const counter = (count) => html`
<style>
.counter { display: flex; align-items: center; gap: 1rem; }
</style>
<div class="counter">
<button @click=${() => update(count - 1)}>-</button>
<span>${count}</span>
<button @click=${() => update(count + 1)}>+</button>
</div>
`;
function update(count) {
render(counter(count), document.getElementById('app'));
}
update(0);
</script>
Lab: Componente de Accordion
Crie um Web Component <meu-accordion> com Shadow DOM, slots e eventos customizados.
# Teste em servidor local
npx live-server --port=5500
Web Components são o padrão nativo da plataforma — independentes de framework, funcionam para sempre.