[ad_1]
Ich muss danken Jeremy Keith und sein wunderbar aufschlussreicher Artikel vom Ende letzten Jahres, das mich mit dem Konzept von HTML-Webkomponenten bekannt machte. Das war für mich der „Aha!“-Moment:
Wenn Sie vorhandenes Markup in ein benutzerdefiniertes Element einbinden und dann mit JavaScript ein neues Verhalten anwenden, tun Sie technisch gesehen nichts, was Sie nicht schon vorher mit DOM-Traversierung und Ereignisbehandlung hätten tun können. Aber es ist weniger anfällig, dies mit einer Webkomponente zu tun. Es ist portabel. Es gehorcht dem Single-Responsibility-Prinzip. Es tut nur eine Sache, aber die tut es gut.
Bis dahin war ich der falschen Annahme, dass alle Webkomponenten basieren ausschließlich auf der Präsenz von JavaScript in Verbindung mit dem ziemlich beängstigend klingenden Schatten-DOM. Es ist zwar tatsächlich möglich, Webkomponenten auf diese Weise zu erstellen, aber es gibt noch eine andere Möglichkeit. Eine bessere vielleicht? Besonders wenn Sie, wie ich, für Progressive Verbesserung. HTML-Webkomponenten sind letztendlich nur HTML.
Obwohl es nicht in den Rahmen dessen fällt, was wir hier diskutieren, hat Andy Bell kürzlich einen Artikel verfasst, der seine (exzellente) Interpretation dessen, was progressive Verbesserung bedeutet.
Sehen wir uns drei konkrete Beispiele an, die die meiner Meinung nach wichtigsten Funktionen von HTML-Webkomponenten demonstrieren – CSS-Stilkapselung und Möglichkeiten zur schrittweisen Verbesserung – ohne dass man sich auf JavaScript verlassen muss, um sofort einsatzbereit zu sein. Wir werden auf jeden Fall JavaScript verwenden, aber die Komponenten sollten auch ohne JavaScript funktionieren.
Die Beispiele finden Sie alle in meinem Web-UI-Boilerplate-Komponentenbibliothek (erstellt mit Storybook), zusammen mit dem zugehöriger Quellcode in GitHub.
Mir gefällt wirklich, wie Chris Ferdinandi lehrt Erstellen einer Webkomponente von Grund aufam Beispiel eines Offenlegungsmusters (Anzeigen/Ausblenden). Dieses erste Beispiel erweitert seine Demo.
Beginnen wir mit dem erstklassigen Bürger, HTML. Webkomponenten ermöglichen es uns, benutzerdefinierte Elemente mit unseren eigenen Namen zu erstellen, was in diesem Beispiel der Fall ist mit einem
Tag, den wir verwenden, um ein dient zum Anzeigen/Verbergen eines Textblocks und eines
des Textes, den wir anzeigen und ausblenden möchten.
Content to be shown/hidden.
Wenn JavaScript deaktiviert ist oder nicht ausgeführt wird (aus einer Reihe möglicher Gründe), ist die Schaltfläche standardmäßig ausgeblendet — dank der hidden
Attribut darauf – und der Inhalt innerhalb des Div wird einfach standardmäßig angezeigt.
Schön. Das ist ein wirklich einfaches Beispiel für progressive Verbesserung in Aktion. Ein Besucher kann den Inhalt mit oder ohne .
Ich habe erwähnt, dass dieses Beispiel die ursprüngliche Demo von Chris Ferdinandi erweitert. Der Hauptunterschied besteht darin, dass Sie das Element entweder durch Klicken auf die Tastatur schließen können ESC
Taste oder Klicken irgendwo außerhalb des Elements. Das ist, was die beiden [data-attribute]
s auf der
Wir beginnen mit Definieren des benutzerdefinierten Elements damit der Browser weiß, was er mit unserem erfundenen Tag-Namen tun soll:
customElements.define('webui-disclosure', WebUIDisclosure);
Benutzerdefinierte Elemente müssen mit einem Bindestrich als Ident benannt werden, wie etwa
oder was auch immer, aber als Jim Neilsen bemerktdurch Scott Jehlbedeutet das nicht unbedingt, dass der Bindestrich hat zwischen zwei Wörtern wechseln.
Normalerweise bevorzuge ich TypeScript zum Schreiben von JavaScript, um dumme Fehler zu vermeiden und ein gewisses Maß an „defensiver“ Programmierung durchzusetzen. Der Einfachheit halber sieht die Struktur des ES-Moduls der Webkomponente in reinem JavaScript jedoch folgendermaßen aus:
default class WebUIDisclosure extends HTMLElement {
constructor() !this.content) return;
this.setupA11y();
this.trigger?.addEventListener('click', this);
setupA11y() {
// Add ARIA props/state to button.
}
// Handle constructor() event listeners.
handleEvent(e) {
// 1. Toggle visibility of content.
// 2. Toggle ARIA expanded state on button.
}
// Handle event listeners which are not part of this Web Component.
connectedCallback() {
document.addEventListener('keyup', (e) => {
// Handle ESC key.
});
document.addEventListener('click', (e) => {
// Handle clicking outside.
});
}
disconnectedCallback() {
// Remove event listeners.
}
}
Wundern Sie sich über diese Event-Listener? Der erste ist definiert in constructor()
Funktion, während der Rest in der connectedCallback()
Funktion. Hawk Ticehurst erläutert die Gründe viel eloquenter als ich.
Dieses JavaScript ist nicht erforderlich, damit die Webkomponente „funktioniert“, aber es fügt einige nette Funktionen hinzu, ganz zu schweigen von den Zugänglichkeitsüberlegungen, um bei der fortschreitenden Verbesserung zu helfen, die es ermöglicht, um den Inhalt anzuzeigen und auszublenden. Beispielsweise fügt JavaScript die entsprechenden
aria-expanded
Und aria-controls
Attribute, die es Benutzern von Bildschirmleseprogrammen ermöglichen, den Zweck der Schaltfläche zu verstehen.
Das ist der Teil der progressiven Verbesserung in diesem Beispiel.
Der Einfachheit halber habe ich für diese Komponente kein zusätzliches CSS geschrieben. Die angezeigten Stile werden einfach von vorhandenen globalen Bereichen oder Komponentenstilen übernommen (z. B. Typografie und Schaltfläche).
Das nächste Beispiel tut haben Sie einige CSS mit zusätzlichem Gültigkeitsbereich.
Das erste Beispiel zeigt die Vorteile der progressiven Verbesserung von HTML-Webkomponenten. Ein weiterer Vorteil ist, dass CSS-Stile gekapselt, Das ist eine elegante Art zu sagen, dass das CSS nicht aus der Komponente austritt. Die Stile sind ausschließlich auf die Webkomponente beschränkt und geraten nicht mit anderen Stilen in Konflikt, die auf der aktuellen Seite angewendet werden.
Wenden wir uns einem zweiten Beispiel zu, das diesmal die stilverkapselnden Fähigkeiten von Webkomponenten demonstriert. Und wie sie die fortschreitende Verbesserung der Benutzererfahrung unterstützen. Wir verwenden eine Komponente mit Registerkarten, um Inhalte in „Panels“ zu organisieren, die angezeigt werden, wenn auf die entsprechende Registerkarte eines Panels geklickt wird – so etwas finden Sie in vielen Komponentenbibliotheken.

Beginnen wir mit der HTML-Struktur:
1 - Lorem ipsum dolor sit amet consectetur.
2 - Lorem ipsum dolor sit amet consectetur.
3 - Lorem ipsum dolor sit amet consectetur.
Sie verstehen, was ich meine: drei Links im Tab-Stil, die beim Anklicken ein Tab-Panel mit Inhalt öffnen. Beachten Sie, dass jeder [data-tab]
in der Registerkartenliste wird auf einen Ankerlink verwiesen, der mit der ID eines Registerkartenbereichs übereinstimmt, z. B. #tab1
, #tab2
usw.
Wir werden uns zuerst die Stilkapselung ansehen, da wir im letzten Beispiel nicht darauf eingegangen sind. Nehmen wir an, das CSS ist folgendermaßen organisiert:
webui-tabs {
[data-tablist] {
/* Default styles without JavaScript */
}
[data-tab] {
/* Default styles without JavaScript */
}
[role="tablist"] {
/* Style role added by JavaScript */
}
[role="tab"] {
/* Style role added by JavaScript */
}
[role="tabpanel"] {
/* Style role added by JavaScript */
}
}
Sehen Sie, was hier passiert? Wir haben zwei Stilregeln — [data-tablist]
Und [data-tab]
— die die Standardstile der Webkomponente enthalten. Mit anderen Worten, diese Stile sind vorhanden, unabhängig davon, ob JavaScript geladen wird oder nicht. Die anderen drei Stilregeln sind Selektoren, die in die Komponente eingefügt werden, solange JavaScript aktiviert und unterstützt wird. Auf diese Weise Die letzten drei Stilregeln werden nur angewendet, wenn JavaScript den **role**
Attribut für diese Elemente im HTML. Genau dort sorgen wir bereits für einen Hauch progressiver Verbesserung, indem wir Stile nur dann festlegen, wenn JavaScript benötigt wird.
Alle diese Stile sind vollständig gekapselt oder auf den
Webkomponente. Es gibt sozusagen keine „Leckage“, die in die Stile anderer Webkomponenten oder sogar in irgendetwas anderes auf der Seite innerhalb des globalen Bereichs eindringen würde. Wir können sogar auf Klassennamen, komplexe Selektoren und Methoden wie BEM zugunsten einfacher Nachkommenselektoren für die untergeordneten Elemente der Komponente, wodurch wir Stile deklarativer auf semantischen Elementen schreiben können.
Kurz zusammengefasst: „Light“ DOM versus Shadow DOM
Für die meisten Webprojekte bevorzuge ich generell die Bündelung von CSS (einschließlich der Webkomponente Sass Partials) in eine einzelne CSS-Datei, sodass die Standardformate der Komponente im globalen Bereich verfügbar sind, auch wenn das JavaScript nicht ausgeführt wird.
Es ist jedoch möglich, ein Stylesheet über JavaScript zu importieren, das wird nur von dieser Webkomponente verwendet wenn JavaScript verfügbar ist:
import styles from './styles.css';
class WebUITabs extends HTMLElement {
constructor() {
super();
this.adoptedStyleSheets = [styles];
}
}
customElements.define('webui-tabs', WebUITabs);
Alternativ könnten wir ein
Ganz gleich, für welche Methode Sie sich entscheiden, der Umfang dieser Stile ist direkt auf die Webkomponente beschränkt. Dadurch wird verhindert, dass Komponentenstile nach außen dringen, globale Stile jedoch übernommen werden können.
Betrachten Sie nun dieses einfache Beispiel. Alles, was wir zwischen den öffnenden und schließenden Tags der Komponente schreiben, wird als Teil des „Light“-DOM betrachtet.
Some content... styles are inherited from the global scope
----------- Shadow DOM Boundary -------------
| |
---------------------------------------------
Dave Rupert hat einen ausgezeichneten Bericht Dadurch ist es wirklich einfach zu sehen, wie externe Stile in der Lage sind, den Shadow DOM zu „durchdringen“ und ein Element im Light DOM auszuwählen. Beachten Sie, wie die Element, das zwischen den Tags des benutzerdefinierten Elements geschrieben wird, erhält die
button
Selector-Stile im globalen CSS, während die über JavaScript eingefügte Elemente bleiben unberührt.
Wenn wir den Shadow DOM stylen wollen Wir müssten das mit internen Stilen tun, wie in den obigen Beispielen zum Importieren eines Stylesheets oder zum Einfügen eines Inline