Web Components Custom Elements
Web Components: Eigene HTML-Elemente erstellen
Web Components ermöglichen Framework-unabhängige, wiederverwendbare UI-Komponenten. Lernen Sie Custom Elements, Shadow DOM und HTML Templates.
Die drei Säulen von Web Components
| Technologie | Funktion |
|---|---|
| Custom Elements | Eigene HTML-Tags definieren |
| Shadow DOM | Gekapseltes CSS und DOM |
| HTML Templates | Wiederverwendbare Markup-Vorlagen |
Einfaches Custom Element
// my-greeting.js
class MyGreeting extends HTMLElement {
constructor() {
super();
this.innerHTML = `<p>Hallo, Welt!</p>`;
}
}
// Element registrieren (Name muss Bindestrich enthalten!)
customElements.define('my-greeting', MyGreeting);
<!-- Verwendung im HTML --> <my-greeting></my-greeting>
Custom Element mit Attributen
class UserCard extends HTMLElement {
// Beobachtete Attribute definieren
static get observedAttributes() {
return ['name', 'email', 'avatar'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// Wird aufgerufen wenn Element ins DOM eingefügt wird
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
// Wird aufgerufen wenn beobachtetes Attribut sich ändert
if (oldValue !== newValue) {
this.render();
}
}
render() {
const name = this.getAttribute('name') || 'Unbekannt';
const email = this.getAttribute('email') || '';
const avatar = this.getAttribute('avatar') || 'default.png';
this.shadowRoot.innerHTML = `
<style>
.card {
display: flex;
align-items: center;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
gap: 1rem;
}
img {
width: 50px;
height: 50px;
border-radius: 50%;
}
h3 { margin: 0; }
p { margin: 0; color: #666; }
</style>
<div class="card">
<img src="${avatar}" alt="${name}">
<div>
<h3>${name}</h3>
<p>${email}</p>
</div>
</div>
`;
}
}
customElements.define('user-card', UserCard);
<!-- Verwendung -->
<user-card
name="Max Mustermann"
email="max@example.com"
avatar="avatar.jpg">
</user-card>
Shadow DOM erklärt
class IsolatedComponent extends HTMLElement {
constructor() {
super();
// Shadow DOM erstellen
// mode: 'open' = von außen zugänglich via element.shadowRoot
// mode: 'closed' = komplett isoliert
const shadow = this.attachShadow({ mode: 'open' });
// Styles im Shadow DOM sind GEKAPSELT
// Sie beeinflussen nicht den Rest der Seite
shadow.innerHTML = `
<style>
/* Diese Styles gelten NUR innerhalb des Shadow DOM */
p { color: red; font-weight: bold; }
.container { padding: 20px; background: #f0f0f0; }
</style>
<div class="container">
<p>Dieser Text ist rot - nur hier!</p>
</div>
`;
}
}
HTML Templates und Slots
<!-- Template im HTML definieren -->
<template id="card-template">
<style>
.card {
border: 2px solid #333;
border-radius: 10px;
padding: 1rem;
}
.header {
background: #333;
color: white;
padding: 0.5rem;
margin: -1rem -1rem 1rem -1rem;
border-radius: 8px 8px 0 0;
}
</style>
<div class="card">
<div class="header">
<slot name="title">Standard-Titel</slot>
</div>
<div class="content">
<slot>Standard-Inhalt</slot>
</div>
</div>
</template>
<script>
class FancyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// Template klonen und einfügen
const template = document.getElementById('card-template');
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('fancy-card', FancyCard);
</script>
<!-- Verwendung mit Slots -->
<fancy-card>
<span slot="title">Mein Titel</span>
<p>Dieser Inhalt geht in den Standard-Slot</p>
</fancy-card>
Lifecycle Callbacks
class LifecycleDemo extends HTMLElement {
constructor() {
// Immer zuerst super() aufrufen!
super();
console.log('1. constructor - Element erstellt');
}
connectedCallback() {
// Element wurde ins DOM eingefügt
console.log('2. connectedCallback - Im DOM');
this.startTimer();
}
disconnectedCallback() {
// Element wurde aus DOM entfernt
console.log('3. disconnectedCallback - Aus DOM entfernt');
this.stopTimer();
}
adoptedCallback() {
// Element wurde in neues Dokument verschoben
console.log('4. adoptedCallback - Dokument gewechselt');
}
attributeChangedCallback(name, oldVal, newVal) {
console.log(`5. Attribut "${name}": ${oldVal} → ${newVal}`);
}
static get observedAttributes() {
return ['data-count'];
}
startTimer() {
this.interval = setInterval(() => {
const count = parseInt(this.dataset.count || 0) + 1;
this.dataset.count = count;
}, 1000);
}
stopTimer() {
clearInterval(this.interval);
}
}
Events in Web Components
class ClickCounter extends HTMLElement {
constructor() {
super();
this.count = 0;
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<button>Klicks: <span>0</span></button>
`;
this.button = this.shadowRoot.querySelector('button');
this.span = this.shadowRoot.querySelector('span');
this.button.addEventListener('click', () => {
this.count++;
this.span.textContent = this.count;
// Custom Event nach außen dispatchen
this.dispatchEvent(new CustomEvent('count-changed', {
detail: { count: this.count },
bubbles: true, // Event steigt im DOM auf
composed: true // Event durchdringt Shadow DOM
}));
});
}
}
customElements.define('click-counter', ClickCounter);
<!-- Event von außen abfangen -->
<click-counter id="counter"></click-counter>
<script>
document.getElementById('counter')
.addEventListener('count-changed', (e) => {
console.log('Neuer Count:', e.detail.count);
});
</script>
Styling von außen ermöglichen
class ThemableButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
button {
/* CSS Custom Properties können von außen gesetzt werden */
background: var(--btn-bg, #007bff);
color: var(--btn-color, white);
padding: var(--btn-padding, 10px 20px);
border: none;
border-radius: var(--btn-radius, 5px);
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
</style>
<button><slot>Button</slot></button>
`;
}
}
customElements.define('themable-button', ThemableButton);
<!-- Styling von außen -->
<style>
themable-button {
--btn-bg: #28a745;
--btn-color: white;
--btn-radius: 20px;
}
</style>
<themable-button>Grüner Button</themable-button>
💡 Tipp:
Web Components funktionieren in allen modernen Browsern ohne Framework. Sie sind ideal für Design-Systeme und wiederverwendbare UI-Bibliotheken, die framework-agnostisch sein sollen.