Wir haben in dieser laufenden Serie über Web Components viel über die Interna der Verwendung von CSS diskutiert, aber es gibt einige spezielle Pseudo-Elemente und Pseudo-Klassen, die, wie gute Freunde, bereitwillig Ihren möglicherweise halitischen Atem riechen, bevor Sie mit diesem potenziellen Liebesinteresse sprechen. Sie wissen schon, sie helfen Ihnen, wenn Sie es am dringendsten brauchen. Und so wie ein guter Freund Ihnen einen Atem-Minze reicht, bieten Ihnen diese Pseudo-Elemente und Pseudo-Klassen einige Lösungen sowohl *innerhalb* der Web Component als auch *außerhalb* der Web Component – der Website, auf der die Web Component lebt.
Ich beziehe mich speziell auf die Pseudo-Elemente ::part und ::slotted sowie auf die Pseudo-Klassen :defined, :host und :host-context. Sie geben uns zusätzliche Möglichkeiten zur Interaktion mit Web Components. Lassen Sie uns sie genauer betrachten.
Artikelreihe
- Web Components sind einfacher als man denkt
- 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 Sie denken (Sie sind hier)
Das ::part Pseudo-Element
::part erlaubt es Ihnen kurz gesagt, den Shadow Tree zu durchdringen, was nur meine "Herr der Ringe"-artige Ausdrucksweise dafür ist, dass es Ihnen erlaubt, Elemente *innerhalb* des Shadow DOM von *außerhalb* des Shadow DOM zu stylen. Theoretisch sollten Sie alle Ihre Stile für den Shadow DOM innerhalb des Shadow DOM kapseln, d.h. innerhalb eines <style>-Elements in Ihrem <template>-Element.
Wenn Sie also etwas wie das hier aus dem allerersten Teil dieser Serie haben, in dem Sie ein <h2> in Ihrem <template> haben, sollten Ihre Stile für dieses <h2> alle im <style>-Element sein.
<template id="zprofiletemplate">
<style>
h2 {
font-size: 3em;
margin: 0 0 0.25em 0;
line-height: 0.8;
}
/* other styles */
</style>
<div class="profile-wrapper">
<div class="info">
<h2>
<slot name="zombie-name">Zombie Bob</slot>
</h2>
<!-- other zombie profile info -->
</div>
</template>
Aber manchmal müssen wir ein Element im Shadow DOM basierend auf Informationen stylen, die auf der Seite vorhanden sind. Nehmen wir zum Beispiel an, wir haben eine Seite für jeden Zombie im System der unsterblichen Liebe mit Übereinstimmungen. Wir könnten Profilen eine Klasse hinzufügen, je nachdem, wie nah sie übereinstimmen. Wir könnten dann zum Beispiel den Namen einer Übereinstimmung hervorheben, wenn er/sie/es eine gute Übereinstimmung ist. Die Nähe einer Übereinstimmung würde davon abhängen, wessen Liste potenzieller Übereinstimmungen angezeigt wird, und wir werden diese Informationen erst erfahren, wenn wir auf dieser Seite sind, sodass wir die Funktionalität nicht in die Web Component einbacken können. Da das <h2> sich im Shadow DOM befindet, können wir von außerhalb des Shadow DOM nicht darauf zugreifen oder es stylen, was bedeutet, dass ein Selektor zombie-profile h2 auf der Übereinstimmungsseite nicht funktioniert.
Aber wenn wir die <template>-Markup leicht anpassen, indem wir dem <h2> ein part-Attribut hinzufügen
<template id="zprofiletemplate">
<style>
h2 {
font-size: 3em;
margin: 0 0 0.25em 0;
line-height: 0.8;
}
/* other styles */
</style>
<div class="profile-wrapper">
<div class="info">
<h2 part="zname">
<slot name="zombie-name">Zombie Bob</slot>
</h2>
<!-- other zombie profile info -->
</div>
</template>
Wie ein Spritzer Bianca in den Mund, haben wir nun die Superkräfte, um die Shadow DOM-Barriere zu durchbrechen und diese Elemente von *außerhalb* des <template> zu stylen.
/* External stylesheet */
.high-match::part(zname) {
color: blue;
}
.medium-match::part(zname) {
color: navy;
}
.low-match::part(zname) {
color: slategray;
}
Es gibt viele Dinge zu beachten, wenn es um die Verwendung von CSS ::part geht. Zum Beispiel ist das Stylen eines Elements innerhalb eines Teils nicht möglich.
/* frowny-face emoji */
.high-match::part(zname) span { ... }
Aber Sie können diesem Element ein part-Attribut hinzufügen und es über seinen eigenen Teilenamen stylen.
Was passiert jedoch, wenn wir eine Web Component innerhalb einer anderen Web Component haben? Funktioniert ::part dann immer noch? Wenn die Web Component in der Markup der Seite erscheint, d.h. Sie sie einfügen, funktioniert ::part von der CSS der Hauptseite aus einwandfrei.
<zombie-profile class="high-match">
<img slot="profile-image" src="https://assets.codepen.io/1804713/leroy.png" />
<span slot="zombie-name">Leroy</span>
<zombie-details slot="zdetails">
<!-- Leroy's details -->
</zombie-details>
</zombie-profile>
Aber wenn die Web Component im Template/Shadow DOM ist, kann ::part nicht beide Shadow Trees durchdringen, nur den ersten. Wir müssen ::part ans Licht bringen... sozusagen. Das können wir mit einem exportparts-Attribut tun.
Um dies zu demonstrieren, fügen wir mithilfe einer Web Component einen "Wasserzeichen"-Effekt hinter den Profilen ein. (Warum? Glauben Sie es oder nicht, dies war das am wenigsten verkünstelte Beispiel, das mir einfiel.) Hier sind unsere Vorlagen: (1) die Vorlage für <zombie-watermark> und (2) die gleiche Vorlage für <zombie-profile>, aber mit einem zusätzlichen <zombie-watermark>-Element am Ende.
<template id="zwatermarktemplate">
<style>
div {
text-transform: uppercase;
font-size: 2.1em;
color: rgb(0 0 0 / 0.1);
line-height: 0.75;
letter-spacing: -5px;
}
span {
color: rgb( 255 0 0 / 0.15);
}
</style>
<div part="watermark">
U n d y i n g L o v e U n d y i n g L o v e U n d y i n g L o v e <span part="copyright">©2 0 2 7 U n d y i n g L o v e U n L t d .</span>
<!-- Repeat this a bunch of times so we can cover the background of the profile -->
</div>
</template>
<template id="zprofiletemplate">
<style>
::part(watermark) {
color: rgb( 0 0 255 / 0.1);
}
/* More styles */
</style>
<!-- zombie-profile markup -->
<zombie-watermark exportparts="copyright"></zombie-watermark>
</template>
<style>
/* External styles */
::part(copyright) {
color: rgb( 0 100 0 / 0.125);
}
</style>
Da ::part(watermark) nur einen Shadow DOM über dem <zombie-watermark> liegt, funktioniert es einwandfrei innerhalb der Vorlagenstile von <zombie-profile>. Da wir exportparts="copyright" auf dem <zombie-watermark> verwendet haben, wurde der Copyright-Teil in das Shadow DOM von <zombie-profile> verschoben und ::part(copyright) funktioniert jetzt auch in externen Stilen, aber ::part(watermark) funktioniert nicht außerhalb der Vorlage von <zombie-profile>.
Wir können Teile mit diesem Attribut auch weiterleiten und **umbenennen**.
<zombie-watermark exportparts="copyright: cpyear"></zombie-watermark>
/* Within zombie-profile's shadow DOM */
/* happy-face emoji */
::part(cpyear) { ... }
/* frowny-face emoji */
::part(copyright) { ... }
Strukturelle Pseudo-Klassen (:nth-child etc.) funktionieren auch nicht für Teile, aber zumindest in Safari können Sie Pseudo-Klassen wie :hover verwenden. Lassen Sie uns die Namen von Top-Übereinstimmungen ein wenig animieren und sie schütteln lassen, während sie nach Liebe suchen. Okay, das habe ich gehört und stimme zu, dass es unbeholfen ist. Lassen Sie uns... äh... sie, sagen wir mal, auffälliger machen, mit ein wenig Bewegung.
.high::part(name):hover {
animation: highmatch 1s ease-in-out;
}
Das ::slotted Pseudo-Element
Das CSS-Pseudo-Element ::slotted kam tatsächlich auf, als wir über interaktive Web Components sprachen. Die Grundidee ist, dass ::slotted jeglichen Inhalt in einem slot in einer Web Component darstellt, d.h. das Element, das das slot-Attribut hat. Aber während ::part den Shadow DOM durchdringt, um die Elemente einer Web Component für externe Stile zugänglich zu machen, bleibt ::slotted im <style>-Element in der <template> der Komponente gekapselt und greift auf das Element zu, das technisch außerhalb des Shadow DOM liegt.
In unserer <zombie-profile>-Komponente beispielsweise wird jedes Profilbild über den slot="profile-image" in das Element eingefügt.
<zombie-profile>
<img slot="profile-image" src="photo.jpg" />
<!-- rest of the content -->
</zombie-profile>
Das bedeutet, dass wir auf dieses Bild – sowie auf jedes andere Bild in einem anderen Slot – wie folgt zugreifen können
::slotted(img) {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
Ähnlich könnten wir *alle* Slots mit ::slotted(*) auswählen, unabhängig davon, um welches Element es sich handelt. Seien Sie sich nur bewusst, dass ::slotted ein Element auswählen muss – Textknoten sind immun gegen ::slotted-Zombie-Stile. Und Kinder des Elements im Slot sind unzugänglich.
Die :defined Pseudo-Klasse
:defined passt auf alle definierten Elemente (ich weiß, überraschend, oder?), sowohl eingebaute als auch benutzerdefinierte. Wenn Ihre benutzerdefinierte Komponente wie ein Zombie herumschleicht und die Fragen des Freundes ihrer Freundin nach ihrer "lebenden" Situation vermeidet, möchten Sie vielleicht nicht, dass die Leichen des Inhalts angezeigt werden, während Sie darauf warten, dass sie wieder zum Leben erwachen, äh... laden.
Sie können die Pseudo-Klasse :defined verwenden, um eine Web Component auszublenden, bevor sie verfügbar – oder "definiert" – ist, wie hier:
:not(:defined) {
display: none;
}
Sie können sehen, wie :defined als eine Art Minze im Mund unserer Komponentenstile wirkt und verhindert, dass zerbrochener Inhalt angezeigt wird (oder schlechter Atem austritt), während die Seite noch lädt. Sobald das Element definiert ist, erscheint es automatisch, weil es jetzt, wissen Sie, definiert und nicht *nicht* definiert ist.
Ich habe der Web Component in der folgenden Demo eine setTimeout von fünf Sekunden hinzugefügt. So können Sie sehen, dass die Elemente <zombie-profile> nicht angezeigt werden, solange sie undefiniert sind. Das <h1> und das <div>, das die <zombie-profile>-Komponenten enthält, sind immer noch da. Es ist nur die <zombie-profile>-Web Component, die display: none erhält, da sie noch nicht definiert ist.
Die :host Pseudo-Klasse
Sagen wir, Sie möchten Stiländerungen an der benutzerdefinierten Komponente selbst vornehmen. Während Sie dies von *außerhalb* der benutzerdefinierten Komponente tun könnten (wie das Festziehen dieser N95), wäre das Ergebnis nicht gekapselt, und zusätzliche CSS müsste überallhin übertragen werden, wo diese benutzerdefinierte Komponente platziert wird.
Es wäre dann sehr praktisch, eine Pseudo-Klasse zu haben, die *außerhalb* des Shadow DOM reichen und die Shadow Root auswählen kann. Diese CSS-Pseudo-Klasse ist :host.
In früheren Beispielen dieser Serie habe ich die Breite von <zombie-profile> von der CSS der Hauptseite aus festgelegt, wie hier:
zombie-profile {
width: calc(50% - 1em);
}
Mit :host kann ich jedoch diese Breite von *innerhalb* der Web Component festlegen, wie hier:
:host {
width: calc(50% - 1em);
}
Tatsächlich gab es in meinen Beispielen einen Div mit der Klasse .profile-wrapper, den ich jetzt entfernen kann, da ich die Shadow Root mit :host als Wrapper verwenden kann. Das ist eine schöne Möglichkeit, die Markup zu verschlanken.
Sie können Abwärtsselektoren von :host aus verwenden, aber nur Nachfahren innerhalb des Shadow DOM können zugegriffen werden – nichts, was in Ihre Web Component eingefügt wurde (ohne ::slotted zu verwenden).

Das gesagt, :host ist kein Ein-Trick-Zombie. Es kann auch einen Parameter annehmen, z. B. einen Klassenselektor, und wendet Stile nur an, wenn die Klasse vorhanden ist.
:host(.high) {
border: 2px solid blue;
}
Dies ermöglicht es Ihnen, Änderungen vorzunehmen, falls bestimmte Klassen zum benutzerdefinierten Element hinzugefügt werden.
Sie können auch Pseudo-Klassen dort übergeben, wie :host(:last-child) und :host(:hover).
Die :host-context Pseudo-Klasse
Nun sprechen wir über :host-context. Es ist wie unser Freund :host(), aber auf Steroiden. Während :host Ihnen die Shadow Root gibt, sagt es Ihnen nichts über den Kontext, in dem die benutzerdefinierte Komponente lebt, oder über ihre Eltern- und Ahnen-Elemente.
:host-context hingegen wirft die Hemmungen über Bord und erlaubt Ihnen, den DOM-Baum den Regenbogen hinauf zum Leprechaun im Trikot zu verfolgen. Beachten Sie nur, dass :host-context zum Zeitpunkt des Schreibens in Firefox oder Safari nicht unterstützt wird. Verwenden Sie es also für progressive Verbesserung.
So funktioniert es. Wir teilen unsere Liste von Zombie-Profilen in zwei Divs auf. Das erste Div enthält alle High Zombie Matches mit einer Klasse .bestmatch. Das zweite Div enthält alle Medium und Low Love Matches mit einer Klasse .worstmatch.
<div class="profiles bestmatch">
<zombie-profile class="high">
<!-- etc. -->
</zombie-profile>
<!-- more profiles -->
</div>
<div class="profiles worstmatch">
<zombie-profile class="medium">
<!-- etc. -->
</zombie-profile>
<zombie-profile class="low">
<!-- etc. -->
</zombie-profile>
<!-- more profiles -->
</div>
Nehmen wir an, wir möchten unterschiedliche Hintergrundfarben für die Klassen .bestmatch und .worstmatch anwenden. Das können wir nicht mit nur :host erreichen.
:host(.bestmatch) {
background-color: #eef;
}
:host(.worstmatch) {
background-color: #ddd;
}
Das liegt daran, dass unsere Best- und Worst-Match-Klassen nicht auf unseren benutzerdefinierten Elementen liegen. Was wir wollen, ist in der Lage zu sein, die übergeordneten Elemente der Profile vom Shadow DOM aus auszuwählen. :host-context sticht über die benutzerdefinierte Komponente hinaus, um die "Match"-Klassen, die wir stylen wollen, auszuwählen.
:host-context(.bestmatch) {
background-color: #eef;
}
:host-context(.worstmatch) {
background-color: #ddd;
}
Nun, danke, dass Sie trotz all des schlechten Atems hier geblieben sind. (Sie konnten es nicht wissen, aber als ich über *Ihren* Atem sprach, sprach ich heimlich über *meinen* Atem.)
Wie würden Sie ::part, ::slotted, :defined, :host und :host-context in Ihrer Web Component verwenden? Lassen Sie es mich in den Kommentaren wissen. (Oder wenn Sie Heilmittel gegen chronische Mundgeruch haben, wäre meine Frau *sehr* daran interessiert, mehr zu hören.)
Gute Zusammenfassung!
Das Hover-Animationsbeispiel scheint in keinem der von mir ausprobierten Browser zu funktionieren (Chrome, Chrome Canary, Firefox, Edge). Ich kann nicht herausfinden, ob es einen Tippfehler gibt oder ob es einfach noch nicht verfügbar ist?
Das tut mir leid. Ich hätte es nie erraten, aber es sieht so aus, als ob das
:hoverauf Teile nur in Safari funktioniert. *Schulterzucken* Ich habe den Artikel entsprechend aktualisiert.Es sieht so aus, als ob :host-context noch nicht für Safari und Firefox verfügbar ist (https://caniuse.com/?search=%3Ahost-context). Schade, es sieht super nützlich aus!