Als ich auf einer Konferenz war (als wir so etwas noch tun konnten) und jemand eine Präsentation über Web Components hielt, fand ich es immer ziemlich raffiniert (ja, anscheinend komme ich aus den 1950ern), aber es schien immer kompliziert und übertrieben. Tausend Zeilen JavaScript, um vier Zeilen HTML zu sparen. Der Sprecher überging entweder unweigerlich die Unmengen an JavaScript, um es zum Laufen zu bringen, oder er ging ins quälende Detail und meine Augen wurden glasig, als ich darüber nachdachte, ob mein Taggeld Snacks abdeckte.
Aber in einem kürzlichen Referenzprojekt zur Erleichterung des HTML-Lernens (durch Hinzufügen von Zombies und albernen Witzen, natürlich) beschloss ich als Vervollständiger, dass ich jedes HTML-Element in der Spezifikation behandeln musste. Über diese Konferenzpräsentationen hinaus war dies meine erste Einführung in die Elemente <slot> und <template>. Aber als ich versuchte, etwas Genaues und Ansprechendes zu schreiben, war ich gezwungen, etwas tiefer einzutauchen.
Und ich habe dabei etwas gelernt: Web Components sind viel einfacher, als ich mich erinnere.
Artikelreihe
- Web Components sind einfacher als du denkst (Du bist hier)
- Interaktive Web Components sind einfacher als man denkt
- Die Verwendung von Web Components in WordPress ist einfacher als man denkt
- Standard-Elemente mit Web Components aufpeppen „ist“ einfacher als man denkt
- Kontextsensitive Web Components sind einfacher als man denkt
- Web Component Pseudo-Klassen und Pseudo-Elemente sind einfacher als man denkt
Entweder haben sich Web Components seit dem letzten Mal, als ich mir bei einer Konferenz Snacks erträumte, stark weiterentwickelt, oder ich habe meine anfängliche Angst vor ihnen davon abhalten lassen, sie wirklich zu verstehen – wahrscheinlich beides.
Ich bin hier, um dir zu sagen, dass du – ja, *du* – eine Web Component erstellen kannst. Lass uns für einen Moment unsere Ablenkungen, Ängste und sogar unsere Snacks vor der Tür lassen und es gemeinsam tun.
Beginnen wir mit dem <template>
Ein <template> ist ein HTML-Element, das es uns erlaubt, eine Vorlage zu erstellen – die HTML-Struktur für die Web Component. Eine Vorlage muss kein riesiger Codebrocken sein. Sie kann so einfach sein wie
<template>
<p>The Zombies are coming!</p>
</template>
Das <template>-Element ist wichtig, weil es Dinge zusammenhält. Es ist wie das Fundament eines Gebäudes; es ist die Basis, auf der alles andere aufgebaut wird. Nehmen wir diesen kleinen HTML-Schnipsel als Vorlage für eine <apokalyptische-warnung> Web Component – wissen Sie, als Warnung, wenn die Zombie-Apokalypse vor der Tür steht.
Dann gibt es noch den <slot>
<slot> ist lediglich ein weiteres HTML-Element, genau wie <template>. Aber in diesem Fall passt <slot> an, was die <template> auf der Seite rendert.
<template>
<p>The <slot>Zombies</slot> are coming!</p>
</template>
Hier haben wir das Wort „Zombies“ in das geteilte Markup eingefügt. Wenn wir nichts mit dem Slot machen, wird standardmäßig der Inhalt zwischen den Tags verwendet. Das wäre in diesem Beispiel „Zombies“.
Die Verwendung von <slot> ist ähnlich wie ein Platzhalter. Wir können den Platzhalter wie er ist verwenden oder etwas anderes definieren, das stattdessen dort platziert wird. Das machen wir mit dem Attribut name.
<template>
<p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>
Das Attribut name sagt der Web Component, welcher Inhalt wohin in der Vorlage gehört. Momentan haben wir einen Slot namens whats-coming. Wir gehen davon aus, dass Zombies zuerst in der Apokalypse kommen, aber der <slot> gibt uns die Flexibilität, etwas anderes einzufügen, zum Beispiel wenn es sich um einen Roboter, einen Werwolf oder sogar um eine Web Component-Apokalypse handelt.
Die Komponente verwenden
Technisch gesehen sind wir mit dem „Schreiben“ der Komponente fertig und können sie überall einfügen, wo wir sie verwenden möchten.
<apocalyptic-warning>
<span slot="whats-coming">Halitosis Laden Undead Minions</span>
</apocalyptic-warning>
<template>
<p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>
Siehst du, was wir da gemacht haben? Wir haben die Komponente <apokalyptische-warnung> auf der Seite platziert, genau wie jedes andere <div> oder so. Aber wir haben auch ein <span> hineingeworfen, das auf das Attribut name unseres <slot> verweist. Und was sich zwischen diesem <span> befindet, ist das, was wir anstelle von „Zombies“ einsetzen wollen, wenn die Komponente gerendert wird.
Hier ist ein kleiner Hinweis, der es wert ist, hervorgehoben zu werden: Namen für benutzerdefinierte Elemente müssen einen Bindestrich enthalten. Das ist einfach eines dieser Dinge, die man wissen muss, bevor man anfängt. Die Spezifikation schreibt dies vor, um Konflikte zu vermeiden, falls HTML ein neues Element mit demselben Namen veröffentlicht.
Immer noch dabei? Nicht zu gruselig, oder? Nun, abgesehen von den Zombies. Wir müssen noch ein wenig arbeiten, um den Tausch des <slot> zu ermöglichen, und dort kommen wir zu JavaScript.
Registrierung der Komponente
Wie ich bereits sagte, benötigt man etwas JavaScript, um das alles zum Laufen zu bringen, aber es ist nicht der superkomplexe, tausendzeilige, tiefgehende Code, den ich immer dachte. Hoffentlich kann ich dich auch überzeugen.
Man benötigt eine Konstruktorfunktion, die das benutzerdefinierte Element registriert. Andernfalls ist unsere Komponente wie die Untoten: Sie ist da, aber nicht ganz lebendig.
Hier ist der Konstruktor, den wir verwenden werden
// Defines the custom element with our appropriate name, <apocalyptic-warning>
customElements.define("apocalyptic-warning",
// Ensures that we have all the default properties and methods of a built in HTML element
class extends HTMLElement {
// Called anytime a new custom element is created
constructor() {
// Calls the parent constructor, i.e. the constructor for `HTMLElement`, so that everything is set up exactly as we would for creating a built in HTML element
super();
// Grabs the <template> and stores it in `warning`
let warning = document.getElementById("warningtemplate");
// Stores the contents of the template in `mywarning`
let mywarning = warning.content;
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
}
});
Ich habe detaillierte Kommentare eingefügt, die die Dinge Zeile für Zeile erklären. Außer der letzten Zeile
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
Wir machen hier viel. Zuerst nehmen wir unser benutzerdefiniertes Element (this) und erstellen einen heimlichen Agenten – ich meine, einen Shadow DOM. mode: open bedeutet einfach, dass JavaScript von außerhalb des :root auf die Elemente innerhalb des Shadow DOM zugreifen und diese manipulieren kann, sozusagen wie ein Hintertüreneingang zur Komponente.
Von dort aus wurde der Shadow DOM erstellt und wir fügen einen Knoten hinzu. Dieser Knoten wird eine tiefe Kopie der Vorlage sein, einschließlich aller Elemente und Texte der Vorlage. Mit der an den Shadow DOM des benutzerdefinierten Elements angehängten Vorlage übernehmen der <slot> und das Attribut slot die Aufgabe, Inhalte zuzuordnen, wo sie hingehören.
Schau dir das an. Jetzt können wir zwei Instanzen derselben Komponente einfügen, die unterschiedliche Inhalte rendern, indem wir einfach ein Element ändern.
Styling der Komponente
Du hast vielleicht Styling in dieser Demo bemerkt. Wie du vielleicht erwartet hast, haben wir absolut die Möglichkeit, unsere Komponente mit CSS zu stylen. Tatsächlich können wir ein <style>-Element direkt in der <template> einschließen.
<template id="warningtemplate">
<style>
p {
background-color: pink;
padding: 0.5em;
border: 1px solid red;
}
</style>
<p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>
Auf diese Weise sind die Stile direkt auf die Komponente beschränkt und dank des Shadow DOM dringt nichts nach außen zu anderen Elementen auf derselben Seite.
In meinem Kopf habe ich angenommen, dass ein benutzerdefiniertes Element eine Kopie der Vorlage nimmt, den von dir hinzugefügten Inhalt einfügt und ihn dann mithilfe des Shadow DOM auf die Seite injiziert. Während es auf dem Frontend so aussieht, ist das nicht, wie es im DOM tatsächlich funktioniert. Der Inhalt einer benutzerdefinierten Komponente bleibt, wo er ist, und der Shadow DOM wird sozusagen wie eine Überlagerung darüber gelegt.

Und da der Inhalt technisch gesehen *außerhalb* der Vorlage liegt, haben alle nachfolgenden Selektoren oder Klassen, die wir im <style>-Element der Vorlage verwenden, keine Auswirkungen auf den eingefügten Inhalt. Dies ermöglicht keine vollständige Kapselung, wie ich es gehofft oder erwartet hatte. Aber da eine benutzerdefinierte Komponente *ein* Element ist, können wir sie als Elementselektor in jeder beliebigen CSS-Datei verwenden, einschließlich des Haupt-Stylesheets, das auf einer Seite verwendet wird. Und obwohl das eingefügte Material technisch gesehen nicht in der Vorlage ist, ist es *in* der benutzerdefinierten Komponente, und nachfolgende Selektoren aus dem CSS funktionieren.
apocalyptic-warning span {
color: blue;
}
Aber Vorsicht! Stile in der Haupt-CSS-Datei können nicht auf Elemente in der <template> oder im Shadow DOM zugreifen.
Fassen wir das alles zusammen
Betrachten wir ein Beispiel, sagen wir ein Profil für einen Zombie-Dating-Service, wie man ihn nach der Apokalypse vielleicht brauchen könnte. Um sowohl den Standardinhalt als auch jeden eingefügten Inhalt zu stylen, benötigen wir *sowohl* ein <style>-Element in der <template> *als auch* Styling in einer CSS-Datei.
Der JavaScript-Code ist exakt derselbe, außer dass wir jetzt mit einem anderen Komponentennamen arbeiten, <zombie-profile>.
customElements.define("zombie-profile",
class extends HTMLElement {
constructor() {
super();
let profile = document.getElementById("zprofiletemplate");
let myprofile = profile.content;
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(myprofile.cloneNode(true));
}
}
);
Hier ist die HTML-Vorlage, einschließlich des gekapselten CSS
<template id="zprofiletemplate">
<style>
img {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
h2 {
font-size: 3em;
margin: 0 0 0.25em 0;
line-height: 0.8;
}
h3 {
margin: 0.5em 0 0 0;
font-weight: normal;
}
.age, .infection-date {
display: block;
}
span {
line-height: 1.4;
}
.label {
color: #555;
}
li, ul {
display: inline;
padding: 0;
}
li::after {
content: ', ';
}
li:last-child::after {
content: '';
}
li:last-child::before {
content: ' and ';
}
</style>
<div class="profilepic">
<slot name="profile-image"><img src="https://assets.codepen.io/1804713/default.png" alt=""></slot>
</div>
<div class="info">
<h2><slot name="zombie-name" part="zname">Zombie Bob</slot></h2>
<span class="age"><span class="label">Age:</span> <slot name="z-age">37</slot></span>
<span class="infection-date"><span class="label">Infection Date:</span> <slot name="idate">September 12, 2025</slot></span>
<div class="interests">
<span class="label">Interests: </span>
<slot name="z-interests">
<ul>
<li>Long Walks on Beach</li>
<li>brains</li>
<li>defeating humanity</li>
</ul>
</slot>
</div>
<span class="z-statement"><span class="label">Apocalyptic Statement: </span> <slot name="statement">Moooooooan!</slot></span>
</div>
</template>
Hier ist das CSS für unser <zombie-profile>-Element und seine Nachkommen aus unserer Haupt-CSS-Datei. Beachten Sie die Duplizierung, um sicherzustellen, dass sowohl die ersetzten Elemente als auch die Elemente aus der Vorlage gleich gestylt werden.
zombie-profile {
width: calc(50% - 1em);
border: 1px solid red;
padding: 1em;
margin-bottom: 2em;
display: grid;
grid-template-columns: 2fr 4fr;
column-gap: 20px;
}
zombie-profile img {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
zombie-profile li, zombie-profile ul {
display: inline;
padding: 0;
}
zombie-profile li::after {
content: ', ';
}
zombie-profile li:last-child::after {
content: '';
}
zombie-profile li:last-child::before {
content: ' and ';
}
Alles zusammen jetzt!
Obwohl es immer noch ein paar Haken und andere Nuancen gibt, hoffe ich, dass du dich jetzt ermächtiger fühlst, mit Web Components zu arbeiten, als du es vor ein paar Minuten warst. Tauche deine Zehen ein, wie wir es hier getan haben. Streue vielleicht eine benutzerdefinierte Komponente in deine Arbeit ein und her, um ein Gefühl dafür zu bekommen und wo sie Sinn macht.
Das ist wirklich alles. Nun, wovor hast du mehr Angst, vor Web Components oder vor der Zombie-Apokalypse? Ich hätte das vielleicht in der nicht allzu fernen Vergangenheit über Web Components gesagt, aber jetzt bin ich stolz zu sagen, dass Zombies das Einzige sind, was mich beunruhigt (nun, das und ob mein Taggeld Snacks abdeckt…)
Bezüglich der Benennung eines benutzerdefinierten Elements, hier können Sie prüfen, ob es gültig und nicht mit einem anderen Framework verwendet wird.
https://raw.githack.com/nuxodin/web-namespace-registry/main/web/index.html
Schön. Danke für das Teilen dieser Ressource!
Schön! Aber was ist mit der Browserunterstützung?
Ziemlich gut, tatsächlich. Nicht im untoten Browser, der IE, aber sie funktionieren so ziemlich überall sonst. https://caniuse.com/custom-elementsv1
Guter Beitrag!
Was die Bindestriche in benutzerdefinierten Elementnamen angeht, würde ich nicht sagen, dass die Spezifikation noch in der Entwicklung ist; dieser Teil ist seit Jahren wirklich stabil und wird sich wahrscheinlich nicht ändern. Neue Web Components-Spezifikationen sind jedoch in Arbeit, wie AOM usw.
Guter Fang. Die allgemeine Spezifikation war mehr in der Entwicklung als die Bindestriche, aber das war aus dem Kontext nicht klar. Danke für die Klärung!
Netter Artikel! Ich schätze es, dass du dir die Zeit genommen hast, zu erklären, wie Web Components funktionieren!
Allerdings habe ich mich am Ende immer noch gefragt: „Warum sollte ich das verwenden?“
Das meine ich nicht kritisch. Ich denke, du hast es großartig erklärt. Ich versuche nur, eine reale Situation zu finden, in der es sinnvoll wäre, es zu verwenden.
Im Beispiel hatte jedes davon ziemlich viel HTML, so dass es nicht so aussah, als hätten wir sehr viel Code gespart, aber es fügte eine weitere Abstraktionsebene und eine JavaScript-Abhängigkeit hinzu.
Normalerweise würde ich so etwas mit PHP machen und es dann mit CSS stylen. Aber ich kann nicht herausfinden, warum ich eine Web Component darüber legen sollte.
Ich glaube, ich verpasse wahrscheinlich etwas.
Kannst du dir einige einfache reale Anwendungen vorstellen? Ich würde gerne deine Gedanken dazu hören.
Das Hinzufügen interaktiver Elemente ist mit Web Components viel einfacher, als sie mit reinem Vanilla JS zu schreiben. WCs sind sozusagen die native Version von React.
Ich habe mich genau dasselbe gefragt (während ich an Snacks dachte) und ich würde zustimmen, dass PHP viele der niedrigstufigen Anwendungsfälle abdecken kann. Aber es gibt ein paar Bereiche, in denen ich denke, dass dies glänzen könnte.
Wenn Sie CSS-Stile isolieren müssen. Obwohl es keine perfekte Kapselung ist, wie ich oben erwähnt habe, können Sie ziemlich viel isolieren. Die einzige andere Möglichkeit, dies zu tun, sind iframes.
Konsistenz über Kontexte hinweg. Ja, das kann auch mit PHP/einer serverseitigen Sprache gemacht werden, aber wenn es einige Kontexte von 1 und/oder Sie wollen, um 2 zu tun, können Sie größere Konsistenz über diese Kontexte hinweg haben.
Ich denke gerne an Web Components als Werkzeug in deinem Werkzeuggürtel. Sie werden nicht jedes Problem lösen, aber manchmal sind sie genau die richtige Lösung für dein Problem.
Komponenten sind sehr ähnlich wie die PHP-Vorlagen, an die du gewöhnt bist. Sie sparen Code nur, wenn du mehrere Instanzen davon auf derselben Seite oder auf vielen Seiten verwendest.
Am wichtigsten ist, dass sie dir auch dann, wenn sie nicht die Codezeilenanzahl einsparen, eine Möglichkeit bieten, dein HTML in einfachere Blöcke aufzuteilen, wodurch es leichter zu verwalten und zu lesen ist.
Stell dir eine 4000 Zeilen lange PHP-Datei vor, die würdest du irgendwie aufräumen wollen, oder?
Großen Codeblöcken Namen zu geben (z. B. zombie-profile) macht es viel einfacher zu lesen und beim Suchen nach Dingen zu überspringen.
Das gekapselte CSS ist etwas, worüber sich viele Leute freuen. Persönlich würde ich es vorziehen, eine Möglichkeit zu haben, den Stil aller meiner Komponenten aus einem globalen CSS zu ändern, aber das ist nur meine Meinung.
Web Components sind ein guter Anwendungsfall für ein Designsystem. Wenn du in einem größeren Team arbeitest und verschiedene JavaScript-Bibliotheken in verschiedenen Teams verwendet werden sollen, kann es schwierig sein, Konsistenz im Komponentendesign zu erreichen.
Du kannst eine Web Component in jedem Framework verwenden, z.B. React oder Vue. Die Entwickler fügen einfach "". Die Komponente bringt das Design mit. Du kannst Änderungen an deiner Web Component-Bibliothek aktualisieren und alle Teams erhalten das neue Design, wenn sie die Bibliothek erneut von NPM oder über CDN laden.
Hallo, toller Artikel! Ich wurde von deinem Tutorial inspiriert, es auf Observable nachzubilden, um zu lernen, wie es funktioniert. Hier ist ein Link https://observablehq.com/@triptych/web-component-test
Danke dafür! Ich werde Web Components jetzt öfter verwenden wegen dieses Artikels.
Ich würde sagen, dass das Erlernen von Web Components viel einfacher ist als das Erlernen eines neuen Frameworks/einer neuen Bibliothek heutzutage.
Ich habe sogar ein paar eigene gemacht.
Hier ist meine Schalter-Komponente
Sehr gute Informationen für App-Entwickler. Diese Hybrid-Tools machen es schneller, die App für alle Plattformen gleichzeitig zu entwickeln. Die Funktionen werden sich im Laufe der Zeit langsam verbessern. Dennoch ist es ein guter Anfang, um Apps schneller zu entwickeln.
Danke für den Artikel! Sehr informativ und unterhaltsam :) Dieses Beispiel könnte ein guter Anwendungsfall für das CSS-Pseudoelement ::slotted() sein, das es Ihnen ermöglicht, den Inhalt der Slots von innerhalb des Shadow DOM zu stylen https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted
Guter Fund, Rose. Ich hatte ::slotted() noch nie gesehen. Ich hatte mir ::part() angesehen, aber das löste das Problem der Kapselung nicht. Ich schätze, ich sollte aufhören, von Snacks zu träumen, ich habe immer noch viel zu lernen :)
Wenn du Tipps hast, wie man sie in eine Drupal 8 oder 9 Website einbindet, wäre ich sehr daran interessiert, sie zu hören. Danke für die Klärung eines ansonsten komplizierten Themas.
Ich habe Drupal 8 oder 9 nicht verwendet (ich habe Drupal 7 vor ein paar Jahren verwendet, aber diese Fähigkeit ist verkümmert). Meine (wahrscheinlich ultra naive) Annahme wäre, dass du das JS/CSS genauso einbinden würdest, wie du jedes andere JS/CSS in deinem Theme oder Modul einbinden würdest. Tut mir leid, dass ich nicht hilfreicher sein kann.
Leider für meine Organisation und meine anderen wird IE immer noch unterstützt.
Ich hätte gerne Empfehlungen zu Polyfills für die Unterstützung dort. Es scheint, dass es volle Frameworks wie Polymer und dann eine Handvoll anderer Polyfill-Bibliotheken gibt. Anleitung wäre großartig.
Du armer, armer Mann. Ich habe selbst keine Polyfills verwendet, aber webcomponents.org hat einen guten Artikel dazu https://www.webcomponents.org/polyfills
Nostalgischer unnötiger Kommentar, das war früher in IE6 möglich (benutzerdefinierte Elemente und HTC)
Es ist interessant zu sehen, wie Leute einzelne Web Components wie diese erstellen. Viele Leute wollen kein „Framework“ lernen, aber ich glaube, die wahre Stärke von Web Components liegt in Form von etwas Größerem wie einem Designsystem. Ich erstelle Komponenten mit Stencil, was sehr Framework-ähnlich ist – und ich kam mit wenig Erfahrung in Node.js und modernen Werkzeugen an, daher war es eine steile Lernkurve. Was Stencil dir kauft, sind robuste Polyfills für IE11/alte Edge und neuere JS-Features. Ich fühle mich total verwöhnt, die neuesten JS-Features (meistens) verwenden zu können und mir keine Gedanken über Kompatibilität machen zu müssen, da alles zu brauchbarem und polyfilled Browser-JS kompiliert wird.
Toller Artikel. Das macht mich nur noch glücklicher, Svelte zu lernen! Alle Vorteile von Komponenten und die Verwendung von Vanilla JS, aber aus Entwicklungssicht viel einfacher.
Ihr neues Projekt kit.svelte.dev ist besonders aufregend.
Eine Sache, die das nicht anspricht, sind Datenobjektattribute zum Trocknen von Dingen.
Während diese Art von Kapselung schön ist
Das ist besser
Ich habe keine gute Möglichkeit gefunden, dies nativ zu tun, aber es scheint, dass das Lit Framework eine dünne Hülle um Web Components erstellt, um diese Art von Fähigkeiten bereitzustellen. Es mag andere Optionen geben, die ich nicht kenne, aber das ist die vielversprechendste, die ich gesehen habe.
Der Template-Teil ist schrecklich. Wenn man die Komponente auf vielen verschiedenen Seiten teilen möchte, sollte dies mit der JavaScript-Datei geschehen: und man muss einfachen Text dafür verwenden. Es ist einfach schrecklich, zumindest bevor wir einige Compiler-Tools dafür haben.
Oder es „kann“ ein Vorteil sein, dass man unterschiedliche Vorlagen auf verschiedenen Seiten bereitstellen kann, aber… zumindest muss man eine „Standard“-Vorlage bereitstellen.