W3docs

Custom Elements

Custom Elements in JavaScript definieren: Klasse erstellen, mit customElements.define() registrieren, Lifecycle-Callbacks und Attribute nutzen sowie eingebaute Elemente erweitern.

Custom Elements sind eine der zentralen Säulen von Web Components. Sie ermöglichen es, eigene HTML-Tags zu definieren, die durch eine JavaScript-Klasse gestützt werden, und erweitern so das integrierte Vokabular des Browsers um wiederverwendbare, in sich geschlossene Elemente mit eigenem Aufbau, Stil und Verhalten.

Diese Seite behandelt alles, was Sie benötigen, um ein Custom Element zu entwickeln: wie man es definiert und registriert, welche Lifecycle-Callbacks der Browser aufruft, wie man auf Attributänderungen reagiert, wie man eingebaute Elemente erweitert und welche Praktiken Ihre Komponenten robust und zugänglich halten.

Zwei Arten von Custom Elements

Die Spezifikation definiert zwei Varianten, und der Unterschied beeinflusst, wie Sie sie erstellen und verwenden:

  • Autonome Custom Elements erweitern das generische HTMLElement und werden als vollständig neue Tags verwendet: <my-card></my-card>. Dies ist der Standardfall.
  • Angepasste eingebaute Elemente erweitern eine bestimmte eingebaute Klasse (z. B. HTMLButtonElement) und werden mit dem is-Attribut verwendet: <button is="fancy-button">. Sie erben die Barrierefreiheit und das Verhalten des Host-Elements kostenlos.

Eine Regel gilt für beide: Der Tag-Name muss einen Bindestrich enthalten (my-card, nicht mycard). Der Bindestrich signalisiert dem Parser, dass es sich um ein Custom Element handelt, und verhindert Kollisionen mit zukünftigen Standard-Tags.

Ein Custom Element definieren

Um ein autonomes Custom Element zu erstellen, definieren Sie eine class, die die eingebaute HTMLElement-Klasse erweitert, und registrieren Sie sie dann mit customElements.define(tagName, class) im Browser. Die Klasse kapselt das Verhalten des Elements; die Registrierung verknüpft sie mit einem Tag-Namen.

Ein gängiges Muster ist es, das interne DOM des Elements innerhalb eines Shadow DOM aufzubauen, damit sein Markup und seine Styles vom Rest der Seite isoliert sind.

Beispiel: Ein einfaches Custom Element erstellen

<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>

Dieses Beispiel definiert ein Custom Element mit dem Namen my-custom-element, das "Hello, World!" innerhalb eines Shadow DOM anzeigt. Um es zu verwenden, fügen Sie einfach <my-custom-element></my-custom-element> an einer beliebigen Stelle in Ihrem HTML ein. Beachten Sie, dass Custom Elements keine selbstschließende Form haben — schreiben Sie immer den passenden schließenden Tag.

Hinweis

Custom Elements v1 wird in allen modernen Browsern unterstützt (Chrome 54+, Firefox 52+, Safari 10.1+, Edge 79+). Überprüfen Sie stets die Browser-Kompatibilität, wenn Sie ältere Umgebungen unterstützen müssen.

Lifecycle-Callbacks

Custom Elements verfügen über eine Reihe von Lifecycle-Callbacks, mit denen Entwickler Code zu bestimmten Zeitpunkten im Lebenszyklus des Elements ausführen können:

  • connectedCallback(): Wird jedes Mal aufgerufen, wenn das Custom Element an ein mit einem Dokument verbundenes Element angehängt wird.
  • disconnectedCallback(): Wird jedes Mal aufgerufen, wenn das Custom Element vom DOM des Dokuments getrennt wird.
  • attributeChangedCallback(name, oldValue, newValue): Wird jedes Mal aufgerufen, wenn eines der Attribute des Custom Elements hinzugefügt, entfernt oder geändert wird.
  • adoptedCallback(): Wird jedes Mal aufgerufen, wenn das Custom Element in ein neues Dokument verschoben wird.
CallbackWann er ausgelöst wird
connectedCallback()Element wird dem DOM hinzugefügt
disconnectedCallback()Element wird aus dem DOM entfernt
attributeChangedCallback(name, oldValue, newValue)Ein beobachtetes Attribut ändert sich
adoptedCallback()Element wird in ein neues Dokument verschoben

Ein nützliches mentales Modell: Der Konstruktor wird einmalig ausgeführt, wenn die Elementinstanz erstellt wird (bevor sie im DOM ist, daher darf er keine Attribute oder Kindelemente berühren), während connectedCallback mehrfach ausgeführt werden kann, wenn das Element hinzugefügt, entfernt und wieder hinzugefügt wird. Führen Sie DOM-abhängige Initialisierungen in connectedCallback durch und bereinigen Sie Listener oder Timer in disconnectedCallback, um Speicherlecks zu vermeiden.

Beispiel: Lifecycle-Callbacks verwenden

<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>

Attribute und Properties

Custom Elements können Attribute und Properties besitzen, um ihren Zustand und ihr Verhalten zu steuern. Attribute werden direkt in HTML gesetzt und sind immer Strings, während Properties am DOM-object des Elements gesetzt werden und jeden Datentyp annehmen können.

Das entscheidende Detail ist attributeChangedCallback: Er wird nur für Attribute ausgelöst, die explizit im static get observedAttributes()-Getter des Elements aufgelistet sind. Wenn ein Attribut nicht in diesem array enthalten ist, löst eine Änderung keinen Callback aus. Eine gängige Konvention ist es, eine Getter/Setter-Property bereitzustellen, die einfach auf ein Attribut abgebildet wird, damit JavaScript-Code und HTML synchron bleiben.

Beispiel: Attribute und Properties verwalten

<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>

Hier aktualisiert das attribute-element seinen Inhalt basierend auf dem data-content-Attribut. Die content-Property bietet eine praktische Möglichkeit, dieses Attribut programmgesteuert abzurufen und zu setzen.

Eingebaute Elemente erweitern

Angepasste eingebaute Elemente erweitern eine bestimmte eingebaute Klasse und werden mit dem is-Attribut verwendet. Der große Vorteil besteht darin, dass sie die Semantik und Barrierefreiheit des Host-Elements erben — ein <button is="fancy-button"> ist für die Tastatur und für Screenreader nach wie vor ein echtes Button-Element.

Beispiel: Ein eingebautes Element erweitern

<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>

Hier erweitert fancy-button das standardmäßige <button>-Element und fügt eine Alert-Meldung hinzu, wenn der Button geklickt wird. Das dritte Argument für customElements.define{ extends: 'button' } — teilt dem Browser mit, auf welchen Tag dieses angepasste Element angewendet wird.

Hinweis

Safari unterstützt keine angepassten eingebauten Elemente (die is=-Form). Für eine breite Kompatibilität bevorzugen Sie autonome Custom Elements und implementieren Sie die benötigte Barrierefreiheit selbst, oder laden Sie ein Polyfill.

Best Practices für Custom Elements

  1. Shadow DOM verwenden: Kapseln Sie immer die interne Struktur und die Styles Ihres Custom Elements mit dem Shadow DOM.
  2. Klare APIs definieren: Stellen Sie klare und intuitive APIs für Ihre Custom Elements durch gut dokumentierte Attribute und Properties bereit.
  3. Lifecycle-Management: Verwalten Sie die Lifecycle-Callbacks des Elements ordnungsgemäß, um ein robustes Verhalten zu gewährleisten und Speicherlecks zu vermeiden.
  4. Barrierefreiheit: Stellen Sie sicher, dass Ihre Custom Elements barrierefrei sind, indem Sie geeignete ARIA-Rollen und -Properties einbinden.
  5. Testen: Testen Sie Ihre Custom Elements gründlich in verschiedenen Browsern und Umgebungen, um Kompatibilität und Stabilität sicherzustellen.

Fazit

Custom Elements bieten eine leistungsstarke Möglichkeit, HTML zu erweitern und die Erstellung von wiederverwendbaren, gekapselten Komponenten mit individuellem Verhalten zu ermöglichen. Durch die Nutzung der Funktionen von Custom Elements — einschließlich Lifecycle-Callbacks, Attribute, Properties und Shadow DOM — können Entwickler anspruchsvolle und wartbare Webanwendungen erstellen.

Beginnen Sie noch heute mit dem Experimentieren mit Custom Elements in Ihren Projekten und erschließen Sie neue Möglichkeiten für die Webentwicklung. Die hier gezeigten Beispiele sind erst der Anfang — nutzen Sie sie als Grundlage, um Ihre eigenen innovativen Custom Elements zu erstellen.

Verwandte Themen

Übungen

Übung
Welche der folgenden Aussagen über Custom Elements in JavaScript sind wahr?
Welche der folgenden Aussagen über Custom Elements in JavaScript sind wahr?
Was this page helpful?