Signatur
Beschreibung
Die schreibgeschützte Eigenschaft window.customElements liefert eine Referenz auf das CustomElementRegistry des aktuellen Dokuments. Über dieses Objekt meldest du eigene Elemente (Custom Elements) beim Browser an. Danach kannst du sie wie normale HTML-Tags verwenden, etwa <user-card>.
Die wichtigsten Methoden des Registry-Objekts sind:
define(name, constructor, options)registriert ein neues Element.get(name)gibt den Konstruktor eines registrierten Elements zurück oderundefined.getName(constructor)liefert umgekehrt den registrierten Namen zu einem Konstruktor.whenDefined(name)gibt ein Promise zurück, das erfüllt wird, sobald das Element definiert ist.upgrade(root)wertet Elemente in einem Teilbaum auf, die noch nicht verbunden sind.
Du brauchst customElements immer dann, wenn du wiederverwendbare, gekapselte UI-Komponenten ohne Framework baust. Oft geschieht das zusammen mit Shadow DOM und <template>. Da customElements eine globale Eigenschaft ist, kannst du im Browser auch einfach customElements statt window.customElements schreiben.
Rückgabewert
Beispiele
Autonomes Custom Element registrieren
class UserCard extends HTMLElement {
connectedCallback() {
const name = this.getAttribute('name') ?? 'Unbekannt';
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<style>p { font-weight: bold; }</style><p></p>`;
shadow.querySelector('p').textContent = `Hallo, ${name}!`;
}
}
customElements.define('user-card', UserCard);
// Verwendung im HTML: <user-card name="Anna"></user-card>
const card = document.createElement('user-card');
card.setAttribute('name', 'Anna');
document.body.append(card);
console.log(card.shadowRoot.textContent.trim());
Doppelte Registrierung vermeiden mit get()
const defineOnce = (name, ctor) => {
if (!customElements.get(name)) {
customElements.define(name, ctor);
console.log(`${name} registriert`);
} else {
console.log(`${name} existiert bereits`);
}
};
class AppBadge extends HTMLElement {}
defineOnce('app-badge', AppBadge);
defineOnce('app-badge', AppBadge);
Auf Definition warten mit whenDefined()
const initDialog = async () => {
await customElements.whenDefined('my-dialog');
const dialog = document.querySelector('my-dialog');
console.log('Bereit:', dialog instanceof customElements.get('my-dialog'));
};
initDialog();
// Später, z. B. nach dynamischem Nachladen eines Moduls:
setTimeout(() => {
document.body.insertAdjacentHTML('beforeend', '<my-dialog></my-dialog>');
customElements.define('my-dialog', class extends HTMLElement {});
}, 500);
Eingebautes Element erweitern (customized built-in)
class ConfirmButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', (e) => {
if (!confirm('Wirklich fortfahren?')) e.preventDefault();
});
}
}
customElements.define('confirm-button', ConfirmButton, { extends: 'button' });
// HTML: <button is="confirm-button">Löschen</button>
const btn = document.createElement('button', { is: 'confirm-button' });
btn.textContent = 'Löschen';
document.body.append(btn);
// Wichtig · Fallstricke
- Namensregeln: Der Elementname muss einen Bindestrich enthalten und mit einem Kleinbuchstaben beginnen, z. B.
my-element. Sonst wirftdefine()einenSyntaxError. - Keine doppelte Registrierung: Wird derselbe Name oder derselbe Konstruktor ein zweites Mal registriert, wirft
define()einenNotSupportedError. Das passiert leicht bei Hot Module Reload oder wenn ein Skript mehrfach geladen wird. Prüfe deshalb vorher mitcustomElements.get(). Einmal registrierte Elemente lassen sich nicht mehr abmelden. - Konstruktor-Einschränkungen: Im
constructormusst du zuerstsuper()aufrufen. Dort solltest du keine Attribute oder Kindelemente lesen oder setzen. Solche Arbeit gehört inconnectedCallback. - Customized built-ins (
{ extends: 'button' }, Attributis) unterstützt Safari/WebKit nicht. Für diese Fälle brauchst du ein Polyfill oder ein autonomes Element. - Geltungsbereich: Jedes Fenster und jedes
<iframe>hat ein eigenes Registry. In Web Workern und in Node.js gibt escustomElementsnicht. - Sicherheit: Wenn du Inhalte über
innerHTMLin den Shadow DOM einfügst, musst du Benutzerdaten maskieren odertextContentverwenden. Sonst droht XSS.