Mit benutzerdefinierten Elementen können Webentwickler neue HTML-Tags definieren, vorhandene Tags erweitern und wiederverwendbare Webkomponenten erstellen.
Mit benutzerdefinierten Elementen können Webentwickler neue HTML-Tags erstellen, vorhandene HTML-Tags optimieren oder die von anderen Entwicklern erstellten Komponenten erweitern. Die API ist die Grundlage von Webkomponenten. Es bietet eine webstandardsbasierte Möglichkeit, wiederverwendbare Komponenten mit nur JS/HTML/CSS zu erstellen. Das Ergebnis ist weniger Code, modularer Code und mehr Wiederverwendung in unseren Apps.
Einführung
Der Browser ist ein hervorragendes Tool zum Strukturieren von Webanwendungen. Es heißt HTML. Vielleicht hast du schon davon gehört! Es ist deklarativ, portabel, gut unterstützt und einfach zu bedienen. HTML ist zwar sehr nützlich, aber sein Vokabular und seine Erweiterbarkeit sind begrenzt. Der HTML-Living-Standard bot bisher keine Möglichkeit, JS-Verhalten automatisch mit Ihrem Markup zu verknüpfen.
Benutzerdefinierte Elemente sind die Antwort auf die Modernisierung von HTML, die fehlenden Teile werden damit ergänzt und Struktur und Verhalten werden kombiniert. Wenn HTML keine Lösung für ein Problem bietet, können wir ein benutzerdefiniertes Element erstellen, das dies tut. Mit benutzerdefinierten Elementen können Sie dem Browser neue Tricks beibringen und gleichzeitig die Vorteile von HTML nutzen.
Neues Element definieren
Um ein neues HTML-Element zu definieren, benötigen wir die Leistungsfähigkeit von JavaScript!
Das globale customElements
wird verwendet, um ein benutzerdefiniertes Element zu definieren und dem Browser ein neues Tag beizubringen. Rufe customElements.define()
mit dem Tag-Namen auf, den du erstellen möchtest, und einem JavaScript-class
, der die Basis-HTMLElement
erweitert.
Beispiel: Definition eines Ausklappmenüs für Mobilgeräte, <app-drawer>
:
class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});
Nutzungsbeispiel:
<app-drawer></app-drawer>
Wichtig: Die Verwendung eines benutzerdefinierten Elements unterscheidet sich nicht von der Verwendung eines <div>
- oder eines anderen Elements. Instanzen können auf der Seite deklariert, dynamisch in JavaScript erstellt werden, Ereignis-Listener können usw. angehängt werden. Hier finden Sie weitere Beispiele.
JavaScript API eines Elements definieren
Die Funktionalität eines benutzerdefinierten Elements wird mithilfe einer ES2015-Funktion class
definiert, die HTMLElement
erweitert. Wenn Sie HTMLElement
erweitern, übernimmt das benutzerdefinierte Element die gesamte DOM API und bedeutet, dass alle Attribute/Methoden, die Sie der Klasse hinzufügen, Teil der DOM-Schnittstelle des Elements werden. Mit der Klasse erstellen Sie im Grunde eine öffentliche JavaScript API für Ihr Tag.
Beispiel: Definition der DOM-Benutzeroberfläche von <app-drawer>
:
class AppDrawer extends HTMLElement {
// A getter/setter for an open property.
get open() {
return this.hasAttribute('open');
}
set open(val) {
// Reflect the value of the open property as an HTML attribute.
if (val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
this.toggleDrawer();
}
// A getter/setter for a disabled property.
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of the disabled property as an HTML attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Can define constructor arguments if you wish.
constructor() {
// If you define a constructor, always call super() first!
// This is specific to CE and required by the spec.
super();
// Setup a click listener on <app-drawer> itself.
this.addEventListener('click', e => {
// Don't toggle the drawer if it's disabled.
if (this.disabled) {
return;
}
this.toggleDrawer();
});
}
toggleDrawer() {
// ...
}
}
customElements.define('app-drawer', AppDrawer);
In diesem Beispiel erstellen wir eine Schublade mit der open
-Property, der disabled
-Property und der toggleDrawer()
-Methode. Außerdem werden Eigenschaften als HTML-Attribute wiedergegeben.
Eine praktische Funktion benutzerdefinierter Elemente ist, dass sich this
in einer Klassendefinition auf das DOM-Element selbst bezieht, also auf die Instanz der Klasse. In unserem Beispiel bezieht sich this
auf <app-drawer>
. So (😉) kann das Element sich selbst einen click
-Listener zuweisen. Sie sind nicht auf Ereignis-Listener beschränkt.
Die gesamte DOM API ist im Elementcode verfügbar. Mit this
können Sie auf die Properties des Elements zugreifen, seine untergeordneten Elemente (this.children
) prüfen, Knoten abfragen (this.querySelectorAll('.items')
) usw.
Regeln zum Erstellen benutzerdefinierter Elemente
- Der Name eines benutzerdefinierten Elements muss einen Bindestrich (-) enthalten.
<x-tags>
,<my-element>
und<my-awesome-app>
sind also gültige Namen,<tabs>
und<foo_bar>
hingegen nicht. Dies ist erforderlich, damit der HTML-Parser benutzerdefinierte Elemente von regulären Elementen unterscheiden kann. Außerdem wird die Aufwärtskompatibilität sichergestellt, wenn HTML-Tags neue Tags hinzugefügt werden. - Sie können ein Tag nur einmal registrieren. Bei einem entsprechenden Versuch wird die Fehlermeldung
DOMException
ausgegeben. Nachdem Sie dem Browser ein neues Tag mitgeteilt haben, ist das erledigt. Keine Rücknahme. - Benutzerdefinierte Elemente können nicht selbstschließend sein, da in HTML nur einige Elemente selbstschließend sein können. Fügen Sie immer ein schließendes Tag (
<app-drawer></app-drawer>
) hinzu.
Reaktionen auf benutzerdefinierte Elemente
Ein benutzerdefiniertes Element kann spezielle Lebenszyklus-Hooks zum Ausführen von Code zu bestimmten Zeitpunkten definieren. Diese werden als Reaktionen auf benutzerdefinierte Elemente bezeichnet.
Name | Wird aufgerufen, wenn |
---|---|
constructor |
Eine Instanz des Elements wird erstellt oder aktualisiert. Nützlich zum Initialisieren des Zustands, zum Einrichten von Ereignis-Listenern oder zum Erstellen eines Schatten-DOMs.
In der Spezifikation finden Sie Einschränkungen für die Verwendung der constructor .
|
connectedCallback |
Wird jedes Mal aufgerufen, wenn das Element in das DOM eingefügt wird. Nützlich zum Ausführen von Einrichtungscode, z. B. zum Abrufen von Ressourcen oder zum Rendern. Im Allgemeinen sollten Sie versuchen, die Arbeit bis dahin zu verschieben. |
disconnectedCallback |
Wird jedes Mal aufgerufen, wenn das Element aus dem DOM entfernt wird. Nützlich zum Ausführen von Code zur Bereinigung. |
attributeChangedCallback(attrName, oldVal, newVal) |
Wird aufgerufen, wenn ein beobachtetes Attribut hinzugefügt, entfernt, aktualisiert oder ersetzt wurde. Wird auch für Anfangswerte aufgerufen, wenn ein Element vom Parser erstellt oder aktualisiert wird. Hinweis: Nur Attribute, die in der Property observedAttributes aufgeführt sind, erhalten diesen Rückruf.
|
adoptedCallback |
Das benutzerdefinierte Element wurde in ein neues document verschoben (z. B. eine Person namens document.adoptNode(el) ).
|
Reaktions-Callbacks sind synchron. Wenn jemand el.setAttribute()
auf Ihrem Element aufruft, ruft der Browser sofort attributeChangedCallback()
auf.
Ebenso erhalten Sie eine disconnectedCallback()
direkt, nachdem Ihr Element aus dem DOM entfernt wurde (z. B. wenn der Nutzer el.remove()
aufruft).
Beispiel: Benutzerdefinierte Elemente als Reaktionen zu <app-drawer>
hinzufügen:
class AppDrawer extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// ...
}
connectedCallback() {
// ...
}
disconnectedCallback() {
// ...
}
attributeChangedCallback(attrName, oldVal, newVal) {
// ...
}
}
Definieren Sie Reaktionen, wenn es Sinn ergibt. Wenn Ihr Element ausreichend komplex ist und in connectedCallback()
eine Verbindung zu IndexedDB öffnet, führen Sie die erforderlichen Bereinigungsarbeiten in disconnectedCallback()
durch. Aber Vorsicht: Sie können nicht davon ausgehen, dass Ihr Element in allen Fällen aus dem DOM entfernt wird. Beispielsweise wird disconnectedCallback()
nie aufgerufen, wenn der Nutzer den Tab schließt.
Eigenschaften und Attribute
Eigenschaften in Attribute widerspiegeln
Es ist üblich, dass HTML-Properties ihren Wert als HTML-Attribut an das DOM zurückgeben. Wenn sich beispielsweise die Werte von hidden
oder id
in JS ändern:
div.id = 'my-id';
div.hidden = true;
Die Werte werden als Attribute auf das Live-DOM angewendet:
<div id="my-id" hidden>
Dies wird als Reflexion von Properties in Attribute bezeichnet. Dies ist bei fast jeder HTML-Property möglich. Warum? Attribute sind auch nützlich, um ein Element deklarativ zu konfigurieren. Bestimmte APIs wie Accessibility und CSS-Auswähltrichter sind auf Attribute angewiesen.
Das Reflektieren einer Property ist überall dort nützlich, wo Sie die DOM-Darstellung des Elements mit dem JavaScript-Status synchronisieren möchten. Eine Möglichkeit, eine Property zu reflektieren, besteht darin, dass benutzerdefiniertes Styling angewendet wird, wenn sich der JS-Status ändert.
Sehen Sie sich unsere <app-drawer>
noch einmal an. Ein Nutzer dieser Komponente kann sie ausblenden und/oder die Nutzerinteraktion verhindern, wenn sie deaktiviert ist:
app-drawer[disabled] {
opacity: 0.5;
pointer-events: none;
}
Wenn die disabled
-Eigenschaft in JS geändert wird, soll dieses Attribut dem DOM hinzugefügt werden, damit die Auswahl des Nutzers übereinstimmt. Das Element kann dieses Verhalten bieten, indem der Wert in ein Attribut mit demselben Namen übernommen wird:
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of `disabled` as an attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
this.toggleDrawer();
}
Änderungen an Attributen beobachten
HTML-Attribute sind eine praktische Möglichkeit für Nutzer, den Anfangsstatus anzugeben:
<app-drawer open disabled></app-drawer>
Elemente können auf Attributänderungen reagieren, indem Sie eine attributeChangedCallback
definieren. Der Browser ruft diese Methode bei jeder Änderung an Attributen auf, die im observedAttributes
-Array aufgeführt sind.
class AppDrawer extends HTMLElement {
// ...
static get observedAttributes() {
return ['disabled', 'open'];
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Only called for the disabled and open attributes due to observedAttributes
attributeChangedCallback(name, oldValue, newValue) {
// When the drawer is disabled, update keyboard/screen reader behavior.
if (this.disabled) {
this.setAttribute('tabindex', '-1');
this.setAttribute('aria-disabled', 'true');
} else {
this.setAttribute('tabindex', '0');
this.setAttribute('aria-disabled', 'false');
}
// TODO: also react to the open attribute changing.
}
}
In diesem Beispiel legen wir zusätzliche Attribute für <app-drawer>
fest, wenn ein disabled
-Attribut geändert wird. Auch wenn wir es hier nicht tun, können Sie attributeChangedCallback
verwenden, um eine JS-Property mit ihrem Attribut zu synchronisieren.
Element-Upgrades
Progressive HTML-Optimierung
Wie Sie bereits wissen, werden benutzerdefinierte Elemente durch Aufrufen von customElements.define()
definiert. Das bedeutet aber nicht, dass Sie ein benutzerdefiniertes Element auf einmal definieren und registrieren müssen.
Benutzerdefinierte Elemente können bevor ihre Definition registriert wird, verwendet werden.
Die progressive Verbesserung ist eine Funktion von benutzerdefinierten Elementen. Mit anderen Worten: Sie können eine Reihe von <app-drawer>
-Elementen auf der Seite deklarieren und customElements.define('app-drawer', ...)
erst viel später aufrufen. Das liegt daran, dass der Browser potenzielle benutzerdefinierte Elemente aufgrund von unbekannten Tags anders behandelt. Das Aufrufen von define()
und das Zuweisen einer Klassendefinition zu einem vorhandenen Element wird als „Element-Upgrade“ bezeichnet.
Mit window.customElements.whenDefined()
können Sie herausfinden, wann ein Tagname definiert wird. Es gibt ein Promise zurück, das aufgelöst wird, sobald das Element definiert ist.
customElements.whenDefined('app-drawer').then(() => {
console.log('app-drawer defined');
});
Beispiel: Arbeiten verzögern, bis eine Reihe von untergeordneten Elementen aktualisiert wurde
<share-buttons>
<social-button type="twitter"><a href="...">Twitter</a></social-button>
<social-button type="fb"><a href="...">Facebook</a></social-button>
<social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');
let promises = [...undefinedButtons].map((socialButton) => {
return customElements.whenDefined(socialButton.localName);
});
// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
// All social-button children are ready.
});
Elementdefinierte Inhalte
Benutzerdefinierte Elemente können ihren eigenen Inhalt mithilfe der DOM-APIs im Elementcode verwalten. Reaktionen eignen sich dafür hervorragend.
Beispiel: Element mit Standard-HTML-Code erstellen:
customElements.define('x-foo-with-markup', class extends HTMLElement {
connectedCallback() {
this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
}
// ...
});
Das Deklarieren dieses Tags führt zu:
<x-foo-with-markup>
<b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>
// TODO: DevSite – Codebeispiel entfernt, da Inline-Ereignis-Handler verwendet wurden
Element mit Shadow-DOM erstellen
Mit Shadow DOM kann ein Element ein DOM-Fragment besitzen, rendern und stylen, das vom Rest der Seite getrennt ist. Sie können sogar eine ganze App in einem einzigen Tag ausblenden:
<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>
Wenn Sie Shadow DOM in einem benutzerdefinierten Element verwenden möchten, rufen Sie this.attachShadow
in Ihrem constructor
auf:
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<style>:host { ... }</style> <!-- look ma, scoped styles -->
<b>I'm in shadow dom!</b>
<slot></slot>
`;
customElements.define('x-foo-shadowdom', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
Nutzungsbeispiel:
<x-foo-shadowdom>
<p><b>User's</b> custom text</p>
</x-foo-shadowdom>
<!-- renders as -->
<x-foo-shadowdom>
#shadow-root
<b>I'm in shadow dom!</b>
<slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>
Benutzerdefinierter Text des Nutzers
// TODO: DevSite - Code sample removed as it used inline event handlers
Elemente aus einem <template>
erstellen
Mit dem <template>
-Element können Sie DOM-Fragmente deklarieren, die beim Laden der Seite geparst werden, inaktiv sind und später zur Laufzeit aktiviert werden können. Es ist ein weiteres API-Primitive in der Familie der Webkomponenten. Vorlagen sind ein idealer Platzhalter, um die Struktur eines benutzerdefinierten Elements anzugeben.
Beispiel: Registrieren eines Elements mit Shadow-DOM-Inhalten, die aus einer <template>
erstellt wurden:
<template id="x-foo-from-template">
<style>
p { color: green; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.</p>
</template>
<script>
let tmpl = document.querySelector('#x-foo-from-template');
// If your code is inside of an HTML Import you'll need to change the above line to:
// let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');
customElements.define('x-foo-from-template', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
</script>
Diese wenigen Codezeilen sind sehr leistungsstark. Sehen wir uns die wichtigsten Dinge an:
- Wir definieren ein neues Element in HTML:
<x-foo-from-template>
- Das Shadow DOM des Elements wird aus einem
<template>
erstellt - Das DOM des Elements ist dank des Shadow DOM lokal für das Element
- Das interne CSS des Elements ist dank des Shadow-DOM auf das Element beschränkt.
Ich befinde mich in Shadow DOM. Mein Markup wurde aus einer <template> gestempelt.
// TODO: DevSite - Code sample removed as it used inline event handlers
Benutzerdefiniertes Element stylen
Auch wenn Ihr Element sein eigenes Design mit Shadow DOM definiert, können Nutzer Ihr benutzerdefiniertes Element auf ihrer Seite stylen. Diese werden als „benutzerdefinierte Stile“ bezeichnet.
<!-- user-defined styling -->
<style>
app-drawer {
display: flex;
}
panel-item {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
panel-item:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > panel-item {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-drawer>
<panel-item>Do</panel-item>
<panel-item>Re</panel-item>
<panel-item>Mi</panel-item>
</app-drawer>
Sie fragen sich vielleicht, wie die CSS-Spezifizität funktioniert, wenn für das Element Stile im Shadow DOM definiert sind. In Bezug auf die Spezifität sind Nutzerstile die beste Wahl. Sie überschreiben immer das elementdefinierte Styling. Weitere Informationen finden Sie im Abschnitt Element mit Shadow DOM erstellen.
Nicht registrierte Elemente vor dem Stylen
Bevor ein Element aktualisiert wird, können Sie es in CSS mit der Pseudoklasse :defined
darauf ausrichten. Das ist nützlich, um eine Komponente vorab zu stylen. So können Sie beispielsweise Layout- oder andere visuelle FOUC verhindern, indem Sie nicht definierte Komponenten ausblenden und sie einblenden, wenn sie definiert sind.
Beispiel: <app-drawer>
ausblenden, bevor sie definiert wird:
app-drawer:not(:defined) {
/* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
display: inline-block;
height: 100vh;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
Nachdem <app-drawer>
definiert wurde, stimmt der Selector (app-drawer:not(:defined)
) nicht mehr überein.
Elemente maximieren
Die Custom Elements API eignet sich nicht nur zum Erstellen neuer HTML-Elemente, sondern auch zum Erweitern anderer benutzerdefinierter Elemente oder sogar des integrierten HTML-Codes des Browsers.
Benutzerdefiniertes Element erweitern
Um ein anderes benutzerdefiniertes Element zu erweitern, müssen Sie die entsprechende Klassendefinition erweitern.
Beispiel: Erstellen Sie <fancy-app-drawer>
, das <app-drawer>
erweitert:
class FancyDrawer extends AppDrawer {
constructor() {
super(); // always call super() first in the constructor. This also calls the extended class' constructor.
// ...
}
toggleDrawer() {
// Possibly different toggle implementation?
// Use ES2015 if you need to call the parent method.
// super.toggleDrawer()
}
anotherMethod() {
// ...
}
}
customElements.define('fancy-app-drawer', FancyDrawer);
Native HTML-Elemente erweitern
Angenommen, Sie möchten eine ausgefallenere <button>
erstellen. Anstatt das Verhalten und die Funktionen von <button>
zu replizieren, ist es besser, das vorhandene Element mithilfe von benutzerdefinierten Elementen schrittweise zu verbessern.
Ein benutzerdefiniertes integriertes Element ist ein benutzerdefiniertes Element, das eines der integrierten HTML-Tags des Browsers erweitert. Der Hauptvorteil der Erweiterung eines vorhandenen Elements besteht darin, alle seine Funktionen (DOM-Attribute, Methoden, Barrierefreiheit) zu nutzen. Die beste Methode zum Erstellen einer progressiven Webanwendung besteht darin, vorhandene HTML-Elemente schrittweise zu verbessern.
Um ein Element zu erweitern, müssen Sie eine Klassendefinition erstellen, die die Einstellungen der richtigen DOM-Schnittstelle übernimmt. Ein benutzerdefiniertes Element, das <button>
erweitert, muss beispielsweise von HTMLButtonElement
und nicht von HTMLElement
erben.
Ebenso muss ein Element, das <img>
erweitert, HTMLImageElement
erweitern.
Beispiel: <button>
erweitern:
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
constructor() {
super(); // always call super() first in the constructor.
this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', (e) => div.remove());
}
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
Beachten Sie, dass sich der Aufruf von define()
geringfügig ändert, wenn ein natives Element erweitert wird. Der erforderliche dritte Parameter gibt dem Browser an, welches Tag Sie erweitern. Das ist notwendig, da viele HTML-Tags dieselbe DOM-Schnittstelle verwenden. HTMLElement
ist beispielsweise für <section>
, <address>
und <em>
identisch. HTMLQuoteElement
ist für <q>
und <blockquote>
identisch usw. Mit {extends: 'blockquote'}
teilt der Browser mit, dass eine optimierte <blockquote>
statt einer <q>
erstellt wird. Eine vollständige Liste der DOM-Schnittstellen von HTML finden Sie in der HTML-Spezifikation.
Nutzer eines benutzerdefinierten integrierten Elements können es auf verschiedene Arten verwenden. Dazu fügen sie dem nativen Tag das Attribut is=""
hinzu:
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
Instanz in JavaScript erstellen:
// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);
oder verwenden Sie den Operator new
:
let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;
Hier ist ein weiteres Beispiel, das <img>
erweitert.
Beispiel: <img>
erweitern:
customElements.define('bigger-img', class extends Image {
// Give img default size if users don't specify.
constructor(width=50, height=50) {
super(width * 10, height * 10);
}
}, {extends: 'img'});
Nutzer deklarieren diese Komponente als:
<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">
oder eine Instanz in JavaScript erstellen:
const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);
Sonstige Details
Unbekannte und nicht definierte benutzerdefinierte Elemente
HTML ist tolerant und flexibel. Wenn Sie beispielsweise <randomtagthatdoesntexist>
auf einer Seite deklarieren, akzeptiert der Browser das problemlos. Warum funktionieren nicht standardmäßige Tags? Die Antwort lautet: Die HTML-Spezifikation erlaubt dies. Elemente, die nicht in der Spezifikation definiert sind, werden als HTMLUnknownElement
geparst.
Das gilt nicht für benutzerdefinierte Elemente. Potenzielle benutzerdefinierte Elemente werden als HTMLElement
geparst, wenn sie mit einem gültigen Namen (einschließlich „-“) erstellt wurden. Dies können Sie in einem Browser prüfen, der benutzerdefinierte Elemente unterstützt. Öffnen Sie die Konsole: Strg + Umschalttaste + J (oder Befehlstaste + Optionstaste + J auf einem Mac) und fügen Sie die folgenden Codezeilen ein:
// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true
// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true
API-Referenz
Das globale customElements
definiert nützliche Methoden für die Arbeit mit benutzerdefinierten Elementen.
define(tagName, constructor, options)
Definiert ein neues benutzerdefiniertes Element im Browser.
Beispiel
customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});
get(tagName)
Gibt den Konstruktor des Elements zurück, wenn ein gültiger Tag-Name für das benutzerdefinierte Element angegeben wurde.
Gibt undefined
zurück, wenn keine Elementdefinition registriert wurde.
Beispiel
let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();
whenDefined(tagName)
Gibt ein Promise zurück, das aufgelöst wird, wenn das benutzerdefinierte Element definiert ist. Wenn das Element bereits definiert ist, beheben Sie das Problem sofort. Wird abgelehnt, wenn der Tag-Name kein gültiger Name für ein benutzerdefiniertes Element ist.
Beispiel
customElements.whenDefined('app-drawer').then(() => {
console.log('ready!');
});
Verlauf und Browserunterstützung
Wenn Sie sich in den letzten Jahren mit Webkomponenten beschäftigt haben, wissen Sie, dass in Chrome 36 und höher eine Version der Custom Elements API implementiert wurde, in der document.registerElement()
anstelle von customElements.define()
verwendet wird. Diese Version gilt jetzt als veraltete Version des Standards und wird als „v0“ bezeichnet.
customElements.define()
ist das neue Ding und wird von Browseranbietern langsam eingeführt. Es heißt Custom Elements v1.
Wenn Sie an der alten v0-Spezifikation interessiert sind, lesen Sie den html5rocks-Artikel.
Unterstützte Browser
Chrome 54 (Status), Safari 10.1 (Status) und Firefox 63 (Status) unterstützen benutzerdefinierte Elemente der Version 1. Die Entwicklung von Edge hat begonnen.
Prüfen Sie, ob Folgendes vorhanden ist, um benutzerdefinierte Elemente zu erkennen:
const supportsCustomElementsV1 = 'customElements' in window;
Polyfill
Bis die Browserunterstützung allgemein verfügbar ist, gibt es eine eigenständige Polyfill-Version für benutzerdefinierte Elemente v1. Wir empfehlen jedoch, das Ladeprogramm von webcomponents.js zu verwenden, um die Polyfills der Webkomponenten optimal zu laden. Das Ladeprogramm nutzt die Feature-Erkennung, um nur die erforderlichen Pollyfills asynchron zu laden, die für den Browser erforderlich sind.
So installierst du es:
npm install --save @webcomponents/webcomponentsjs
Verwendung:
<!-- Use the custom element on the page. -->
<my-element></my-element>
<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>
<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
WebComponents.waitFor(() => {
// At this point we are guaranteed that all required polyfills have
// loaded, and can use web components APIs.
// Next, load element definitions that call `customElements.define`.
// Note: returning a promise causes the custom elements
// polyfill to wait until all definitions are loaded and then upgrade
// the document in one batch, for better performance.
return loadScript('my-element.js');
});
</script>
Fazit
Mit benutzerdefinierten Elementen haben wir ein neues Tool zum Definieren neuer HTML-Tags im Browser und zum Erstellen wiederverwendbarer Komponenten. In Kombination mit den anderen neuen Plattformprimitiven wie Shadow DOM und <template>
ergibt sich ein umfassendes Bild von Web-Komponenten:
- Browserübergreifendes (Webstandard) Tool zum Erstellen und Erweitern wiederverwendbarer Komponenten.
- Für den Einstieg sind keine Bibliotheken oder Frameworks erforderlich. Vanilla JS/HTML FTW!
- Stellt ein vertrautes Programmiermodell bereit. Es ist nur DOM/CSS/HTML.
- Funktioniert gut mit anderen neuen Webplattformfunktionen (Shadow DOM,
<template>
, benutzerdefinierte CSS-Properties usw.) - Nahtlose Integration in die DevTools des Browsers.
- Vorhandene Bedienungshilfen nutzen