Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

CustomStateSet

Baseline 2024
Neu verfügbar

Seit Mai 2024 funktioniert diese Funktion auf aktuellen Geräten und in aktuellen Browserversionen. Auf älteren Geräten oder in älteren Browsern funktioniert sie möglicherweise nicht.

Die Schnittstelle CustomStateSet des Document Object Model speichert eine Liste von Zuständen für ein autonomes benutzerdefiniertes Element. Sie ermöglicht es, Zustände zur Menge hinzuzufügen und daraus zu entfernen.

Mit der Schnittstelle lassen sich die internen Zustände eines benutzerdefinierten Elements verfügbar machen, sodass Code, der das Element verwendet, sie in CSS-Selektoren nutzen kann.

Instanzeigenschaften

CustomStateSet.size

Gibt die Anzahl der Werte im CustomStateSet zurück.

Instanzmethoden

CustomStateSet.add()

Fügt der Menge einen Wert hinzu.

CustomStateSet.clear()

Entfernt alle Elemente aus dem CustomStateSet-Objekt.

CustomStateSet.delete()

Entfernt einen Wert aus dem CustomStateSet-Objekt.

CustomStateSet.entries()

Gibt einen neuen Iterator zurück, der die Werte für jedes Element im CustomStateSet in der Reihenfolge des Einfügens enthält.

CustomStateSet.forEach()

Führt eine übergebene Funktion für jeden Wert im CustomStateSet-Objekt aus.

CustomStateSet.has()

Gibt einen Boolean-Wert zurück, der angibt, ob ein Element mit dem angegebenen Wert vorhanden ist.

CustomStateSet.keys()

Ein Alias für CustomStateSet.values().

CustomStateSet.values()

Gibt ein neues Iterator-Objekt zurück, das die Werte für jedes Element im CustomStateSet-Objekt in der Reihenfolge des Einfügens liefert.

Beschreibung

Integrierte HTML-Elemente können verschiedene Zustände haben, etwa „enabled“ und „disabled“, „checked“ und „unchecked“ oder „initial“, „loading“ und „ready“. Einige dieser Zustände sind öffentlich und können über Eigenschaften oder Attribute gesetzt oder abgefragt werden. Andere sind dagegen intern und können nicht direkt gesetzt werden. Unabhängig davon, ob sie öffentlich oder intern sind, lassen sich Elementzustände im Allgemeinen mithilfe von CSS-Pseudoklassen auswählen und gestalten.

Mit CustomStateSet können Entwickler Zustände für autonome benutzerdefinierte Elemente hinzufügen und entfernen, nicht jedoch für Elemente, die von integrierten Elementen abgeleitet sind. Diese Zustände können anschließend ähnlich wie die Pseudoklassen integrierter Elemente als Selektoren für benutzerdefinierte Zustände verwendet werden.

Zustände benutzerdefinierter Elemente festlegen

Damit CustomStateSet verfügbar ist, muss ein benutzerdefiniertes Element zunächst HTMLElement.attachInternals() aufrufen, um ein ElementInternals-Objekt anzubinden. CustomStateSet wird anschließend von ElementInternals.states zurückgegeben. Beachten Sie, dass ElementInternals nicht an ein benutzerdefiniertes Element angebunden werden kann, das auf einem integrierten Element basiert. Diese Funktion ist daher nur für autonome benutzerdefinierte Elemente verfügbar (siehe github.com/whatwg/html/issues/5166).

Die CustomStateSet-Instanz ist ein Set-ähnliches Objekt, das eine geordnete Menge von Zustandswerten enthalten kann. Jeder Wert ist ein benutzerdefinierter Bezeichner. Bezeichner können der Menge hinzugefügt oder daraus entfernt werden. Ist ein Bezeichner in der Menge enthalten, ist der entsprechende Zustand true; wird er entfernt, ist der Zustand false.

Benutzerdefinierte Elemente mit Zuständen, die mehr als zwei mögliche Werte haben, können diese durch mehrere boolesche Zustände darstellen, von denen jeweils nur einer true ist, also im CustomStateSet enthalten ist.

Die Zustände können innerhalb des benutzerdefinierten Elements verwendet werden, sind aber außerhalb der benutzerdefinierten Komponente nicht direkt zugänglich.

Zusammenspiel mit CSS

Mit der Pseudoklasse für benutzerdefinierte Zustände :state() können Sie ein benutzerdefiniertes Element in einem bestimmten Zustand auswählen. Die Syntax dieser Pseudoklasse lautet :state(my-state-name), wobei my-state-name der im Element definierte Zustand ist. Die Pseudoklasse für benutzerdefinierte Zustände trifft nur dann auf das benutzerdefinierte Element zu, wenn der Zustand true ist, also my-state-name im CustomStateSet enthalten ist.

Das folgende CSS wählt beispielsweise ein benutzerdefiniertes labeled-checkbox-Element aus, wenn dessen CustomStateSet den Zustand checked enthält, und versieht das Kontrollkästchen mit einem solid-Rahmen:

css
labeled-checkbox:state(checked) {
  border: solid;
}

CSS kann auch einen benutzerdefinierten Zustand innerhalb des Shadow DOM eines benutzerdefinierten Elements auswählen, indem :state() innerhalb der Pseudoklassenfunktion :host() angegeben wird.

Außerdem kann die Pseudoklasse :state() nach dem Pseudoelement ::part() verwendet werden, um die Shadow Parts eines benutzerdefinierten Elements auszuwählen, die sich in einem bestimmten Zustand befinden.

Warnung: Browser, die :state() noch nicht unterstützen, verwenden zur Auswahl benutzerdefinierter Zustände einen CSS-Bezeichner vom Typ <dashed-ident>. Diese Syntax ist inzwischen veraltet. Informationen dazu, wie Sie beide Ansätze unterstützen können, finden Sie unten im Abschnitt Kompatibilität mit der <dashed-ident>-Syntax.

Beispiele

Den benutzerdefinierten Zustand eines benutzerdefinierten Kontrollkästchens auswählen

Dieses an die Spezifikation angelehnte Beispiel zeigt ein benutzerdefiniertes Kontrollkästchen mit dem internen Zustand „checked“. Dieser wird dem benutzerdefinierten Zustand checked zugeordnet, sodass die Gestaltung über die Pseudoklasse :state(checked) erfolgen kann.

HTML

Das HTML verwendet das benutzerdefinierte Element, das durch das folgende JavaScript registriert wird:

html
<labeled-checkbox>You need to check this</labeled-checkbox>

CSS

Wir verwenden die Pseudoklasse :state(checked), um das Kontrollkästchen auszuwählen, wenn es aktiviert ist.

css
labeled-checkbox {
  border: dashed red;
}
labeled-checkbox:state(checked) {
  border: solid;
}

JavaScript

Zunächst definieren wir die Klasse LabeledCheckbox, die HTMLElement erweitert. Im Konstruktor rufen wir die Methode super() auf, fügen einen Listener für das Klickereignis hinzu und rufen this.attachInternals() auf, um ein ElementInternals-Objekt anzubinden.

Der Großteil der übrigen Arbeit erfolgt in connectedCallback(), das aufgerufen wird, wenn ein benutzerdefiniertes Element zur Seite hinzugefügt wird. Der Inhalt des Elements wird mithilfe eines <style>-Elements als Text [] oder [x], gefolgt von einer Beschriftung, festgelegt. Bemerkenswert ist hier, dass die Pseudoklasse für benutzerdefinierte Zustände verwendet wird, um den anzuzeigenden Text auszuwählen: :host(:state(checked)). Nach dem folgenden Beispiel erläutern wir genauer, was im Codeausschnitt geschieht.

js
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
    this._boundOnClick = this._onClick.bind(this);
    this.addEventListener("click", this._boundOnClick);

    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
  :host {
    display: block;
  }
  :host::before {
    content: "[ ]";
    white-space: pre;
    font-family: monospace;
  }
  :host(:state(checked))::before {
    content: "[x]";
  }
</style>
<slot>Label</slot>
`;
  }

  get checked() {
    return this._internals.states.has("checked");
  }

  set checked(flag) {
    if (flag) {
      this._internals.states.add("checked");
    } else {
      this._internals.states.delete("checked");
    }
  }

  _onClick(event) {
    // Toggle the 'checked' property when the element is clicked
    this.checked = !this.checked;
  }

  static isStateSyntaxSupported() {
    return CSS.supports("selector(:state(checked))");
  }
}

// Display a warning to unsupported browsers
if (!LabeledCheckbox.isStateSyntaxSupported()) {
  if (!document.getElementById("state-warning")) {
    const warning = document.createElement("div");
    warning.id = "state-warning";
    warning.style.color = "red";
    warning.textContent = "This feature is not supported by your browser.";
    document.body.insertBefore(warning, document.body.firstChild);
  }
}

In der Klasse LabeledCheckbox:

  • In get checked() und set checked() verwenden wir ElementInternals.states, um das CustomStateSet abzurufen.
  • Die Methode set checked(flag) fügt den Bezeichner "checked" zum CustomStateSet hinzu, wenn der Wert gesetzt ist, und entfernt ihn, wenn der Wert false ist.
  • Die Methode get checked() prüft lediglich, ob der Zustand checked in der Menge enthalten ist.
  • Der Eigenschaftswert wird umgeschaltet, wenn auf das Element geklickt wird.

Anschließend rufen wir die Methode define() auf dem von Window.customElements zurückgegebenen Objekt auf, um das benutzerdefinierte Element zu registrieren:

js
customElements.define("labeled-checkbox", LabeledCheckbox);

Ergebnis

Klicken Sie auf das Element, um zu sehen, wie sich der Rahmen ändert, wenn der Zustand checked des Kontrollkästchens umgeschaltet wird.

Einen benutzerdefinierten Zustand in einem Shadow Part eines benutzerdefinierten Elements auswählen

Dieses an die Spezifikation angelehnte Beispiel zeigt, wie benutzerdefinierte Zustände verwendet werden können, um die Shadow Parts eines benutzerdefinierten Elements gezielt zu gestalten. Shadow Parts sind Bereiche des Shadow Tree, die für Seiten, die das benutzerdefinierte Element verwenden, ausdrücklich zugänglich gemacht werden.

Das Beispiel erstellt ein benutzerdefiniertes <question-box>-Element, das eine Frage zusammen mit einem Kontrollkästchen mit der Beschriftung „Yes“ anzeigt. Für das Kontrollkästchen verwendet das Element das im vorherigen Beispiel definierte <labeled-checkbox>.

HTML

Das HTML verwendet das benutzerdefinierte Element, das durch das folgende JavaScript registriert wird.

html
<!-- Question box with default prompt "Question" -->
<question-box></question-box>

<!-- Question box with custom prompt "Continue?" -->
<question-box>Continue?</question-box>

CSS

Der erste CSS-Block wählt den zugänglich gemachten Shadow Part namens checkbox mit dem Selektor ::part() aus und gestaltet ihn standardmäßig in red.

css
question-box::part(checkbox) {
  color: red;
}

Im zweiten Block folgt auf ::part() die Pseudoklasse :state(), um checkbox-Parts im Zustand checked auszuwählen:

css
question-box::part(checkbox):state(checked) {
  color: green;
  outline: dashed 1px green;
}

JavaScript

Zunächst definieren wir die Klasse QuestionBox für das benutzerdefinierte Element, die HTMLElement erweitert. Wie üblich ruft der Konstruktor zuerst die Methode super() auf. Danach binden wir durch einen Aufruf von attachShadow() einen Shadow-DOM-Baum an das benutzerdefinierte Element an.

js
class QuestionBox extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<div><slot>Question</slot></div>
<labeled-checkbox part="checkbox">Yes</labeled-checkbox>
`;
  }
}

Der Inhalt der Shadow Root wird mithilfe von innerHTML festgelegt. Dabei wird ein <slot>-Element definiert, das den Standardtext „Question“ für das Element enthält. Anschließend definieren wir ein benutzerdefiniertes <labeled-checkbox>-Element mit dem Standardtext "Yes". Dieses Kontrollkästchen wird über das Attribut part als Shadow Part der Fragebox mit dem Namen checkbox zugänglich gemacht.

Beachten Sie, dass der Code und die Gestaltung für das <labeled-checkbox>-Element genau dem vorherigen Beispiel entsprechen und deshalb hier nicht wiederholt werden.

Anschließend rufen wir die Methode define() auf dem von Window.customElements zurückgegebenen Objekt auf, um das benutzerdefinierte Element unter dem Namen question-box zu registrieren:

js
customElements.define("question-box", QuestionBox);

Ergebnis

Klicken Sie auf eines der Kontrollkästchen, um zu sehen, wie sich beim Umschalten des Zustands checked die Farbe von red zu green ändert und eine Umrandung erscheint.

Nicht boolesche interne Zustände

Dieses Beispiel zeigt, wie Sie mit einem benutzerdefinierten Element umgehen, das eine interne Eigenschaft mit mehreren möglichen Werten besitzt.

Das benutzerdefinierte Element hat in diesem Fall eine Eigenschaft state mit den zulässigen Werten „loading“, „interactive“ und „complete“. Dazu ordnen wir jeden Wert einem eigenen benutzerdefinierten Zustand zu und stellen im Code sicher, dass nur der Bezeichner gesetzt ist, der dem internen Zustand entspricht. Dies ist in der Implementierung der Methode set state() zu sehen: Wir setzen den internen Zustand, fügen den Bezeichner für den entsprechenden benutzerdefinierten Zustand zum CustomStateSet hinzu und entfernen die Bezeichner für alle anderen Werte.

Der übrige Code ähnelt größtenteils dem Beispiel mit einem einzelnen booleschen Zustand. Während der Benutzer zwischen den Zuständen wechselt, zeigen wir für jeden Zustand einen anderen Text an.

HTML

Das HTML verwendet das benutzerdefinierte Element, das durch das folgende JavaScript registriert wird. Dies ähnelt dem Beispiel mit einem einzelnen booleschen Zustand. Allerdings geben wir keinen Wert an, sondern verwenden den Standardwert aus dem Slot (<slot>Click me</slot>).

html
<many-state-element></many-state-element>

CSS

Im CSS verwenden wir die drei Pseudoklassen für benutzerdefinierte Zustände, um die Gestaltung für jeden internen Zustandswert festzulegen: :state(loading), :state(interactive), :state(complete). Beachten Sie, dass der Code des benutzerdefinierten Elements sicherstellt, dass jeweils nur einer dieser benutzerdefinierten Zustände gesetzt sein kann.

css
many-state-element:state(loading) {
  border: dotted grey;
}
many-state-element:state(interactive) {
  border: dashed blue;
}
many-state-element:state(complete) {
  border: solid green;
}

JavaScript

js
class ManyStateElement extends HTMLElement {
  constructor() {
    super();
    this._boundOnClick = this._onClick.bind(this);
    this.addEventListener("click", this._boundOnClick);
    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    this.state = "loading";

    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
  :host {
    display: block;
    font-family: monospace;
  }
  :host::before {
    content: "[ unknown ]";
    white-space: pre;
  }
  :host(:state(loading))::before {
    content: "[ loading ]";
  }
  :host(:state(interactive))::before {
    content: "[ interactive ]";
  }
  :host(:state(complete))::before {
    content: "[ complete ]";
  }
</style>
<slot>Click me</slot>
`;
  }

  get state() {
    return this._state;
  }

  set state(stateName) {
    // Set internal state to passed value
    // Add identifier matching state and delete others
    if (stateName === "loading") {
      this._state = "loading";
      this._internals.states.add("loading");
      this._internals.states.delete("interactive");
      this._internals.states.delete("complete");
    } else if (stateName === "interactive") {
      this._state = "interactive";
      this._internals.states.delete("loading");
      this._internals.states.add("interactive");
      this._internals.states.delete("complete");
    } else if (stateName === "complete") {
      this._state = "complete";
      this._internals.states.delete("loading");
      this._internals.states.delete("interactive");
      this._internals.states.add("complete");
    }
  }

  _onClick(event) {
    // Cycle the state when element clicked
    if (this.state === "loading") {
      this.state = "interactive";
    } else if (this.state === "interactive") {
      this.state = "complete";
    } else if (this.state === "complete") {
      this.state = "loading";
    }
  }

  static isStateSyntaxSupported() {
    return CSS.supports("selector(:state(loading))");
  }
}

customElements.define("many-state-element", ManyStateElement);

if (!LabeledCheckbox.isStateSyntaxSupported()) {
  if (!document.getElementById("state-warning")) {
    const warning = document.createElement("div");
    warning.id = "state-warning";
    warning.style.color = "red";
    warning.textContent = "This feature is not supported by your browser.";
    document.body.insertBefore(warning, document.body.firstChild);
  }
}

Ergebnisse

Klicken Sie auf das Element, um zu sehen, wie sich der Rahmen beim Wechsel des Zustands ändert.

Kompatibilität mit der <dashed-ident>-Syntax

Früher wurden benutzerdefinierte Elemente mit benutzerdefinierten Zuständen mithilfe eines <dashed-ident> anstelle der Funktion :state() ausgewählt. Browserversionen, die :state() nicht unterstützen, geben einen Fehler aus, wenn ein Bezeichner übergeben wird, dem die beiden Bindestriche vorangestellt fehlen. Wenn diese Browser unterstützt werden müssen, verwenden Sie entweder einen try...catch-Block für beide Syntaxvarianten oder nutzen Sie ein <dashed-ident> als Zustandswert und wählen Sie ihn sowohl mit dem CSS-Selektor :--my-state als auch mit :state(--my-state) aus.

Einen try...catch-Block verwenden

Dieser Code zeigt, wie Sie mit try...catch zunächst versuchen können, einen Zustandsbezeichner hinzuzufügen, der kein <dashed-ident> verwendet, und bei einem Fehler auf <dashed-ident> zurückgreifen.

CSS

css
compatible-state-element:is(:--loaded, :state(loaded)) {
  border: solid green;
}

JavaScript

js
class CompatibleStateElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    // The double dash is required in browsers with the
    // legacy syntax, not supplying it will throw
    try {
      this._internals.states.add("loaded");
    } catch {
      this._internals.states.add("--loaded");
    }
  }
}

Bezeichner mit zwei vorangestellten Bindestrichen verwenden

Alternativ können Sie das <dashed-ident> in JavaScript verwenden. Der Nachteil dieses Ansatzes ist, dass die Bindestriche auch bei Verwendung der CSS-Syntax :state() angegeben werden müssen.

CSS

css
compatible-state-element:is(:--loaded, :state(--loaded)) {
  border: solid green;
}

JavaScript

js
class CompatibleStateElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
  }
  connectedCallback() {
    // The double dash is required in browsers with the
    // legacy syntax, but works with the modern syntax
    this._internals.states.add("--loaded");
  }
}

Spezifikationen

Spezifikation
HTML
# customstateset

Browser-Kompatibilität

Siehe auch

Benutzerdefinierte Elemente verwenden