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

View in English Always switch to English

Anpassbare Auswahl-Listenfelder

Dieser Artikel knüpft an den vorherigen an und zeigt, wie anpassbare Listenfeld-<select>-Elemente gestaltet werden können.

Ein wesentlicher Vorteil von anpassbaren <select>-Listenfeldern gegenüber „klassischen“ Select-Listenfeldern ist, dass Sie alle Teile des Steuerungselements vollständig gestalten können und eine viel größere Vielfalt an Kindelementen darin aufnehmen können, was eine größere Flexibilität bei Design und Funktionalität bedeutet.

Select-Listenfelder vs Dropdown-Selects

Im vorherigen Artikel haben wir über „Dropdown“-<select>-Elemente gesprochen, bei denen es sich um Steuerelemente handelt, die über eine Schaltfläche verfügen, die beim Drücken ein Dropdown-Auswahlfenster anzeigt, aus dem Sie eine Option auswählen können. Diese werden mit grundlegenden HTML-Tags wie <select> angegeben.

Im Gegensatz dazu sind „Listenfeld“-<select>-Elemente Steuerelemente mit einem Feld, das mehrere Optionen gleichzeitig anzeigt, aus denen Sie eine oder mehrere Optionen auswählen können. Sie entscheiden sich für die Darstellung eines „Listenfeld“-Selects, indem Sie das multiple-Attribut (um Mehrfachauswahl zu ermöglichen) und/oder einen size-Wert größer als 1 angeben. Zum Beispiel <select multiple> oder <select size="3">.

Das folgende Live-Beispiel veranschaulicht den Unterschied:

Hinweis: Das multiple-Attribut sowie jeder size-Wert größer als 1 versetzen das <select>-Element in den Listenfeldmodus.

Wie vergleichen sich anpassbare Listenfelder mit anpassbaren Dropdowns?

Ein anpassbares Listenfeld-<select> ist einfacher zu gestalten als die Dropdown-Variante:

  • Es gibt keinen Dropdown-Auswähler, sodass Sie sich nicht um die Gestaltung mit dem ::picker(select)-Pseudoelement oder dessen :open- und Geschlossen-Zustände kümmern müssen.
  • Sie müssen sich nicht um die Gestaltung des Symbols der Auswahlschaltfläche mit ::picker-icon kümmern oder um die Manipulation, wie das aktuell ausgewählte <option> innerhalb der Schaltfläche mit dem <selectedcontent>-Element angezeigt wird.
  • Es gibt nur einen einzigen Container; Sie müssen sich nicht um die Position des Auswählers relativ zur Schaltfläche kümmern.

Ein einfaches angepasstes Listenfeld

Durchgehen wir ein einfaches Beispiel, um zu zeigen, wie ein angepasstes Listenfeld implementiert wird. Das Markup für dieses Beispiel sieht folgendermaßen aus:

html
<p>
  <label for="pet-select">Select pets:</label><br />
  <select id="pet-select" multiple>
    <option value="cat">Cat</option>
    <option value="dog">Dog</option>
    <option value="chicken">Chicken</option>
    <option value="fish">Fish</option>
    <option value="hamster">Hamster</option>
  </select>
</p>

Hier gibt es nichts Bemerkenswertes. Beachten Sie, dass wir unser Listenfeld mit <select multiple> anstelle von <select size="3"> rendern. Der einzige Unterschied besteht darin, dass wir mehrere Optionen anstelle einer einzigen auswählen können. Die Gestaltung funktioniert auf genau die gleiche Weise.

Wir beginnen mit unserer Gestaltung, indem wir das <select>-Element mit einem appearance-Wert von base-select in die benutzerdefinierte Gestaltung übertragen:

css
select {
  appearance: base-select;
}

Damit können wir nun unsere <select>- und <option>-Elemente nach Belieben gestalten.

Unsere grundlegenden Stile sehen wie folgt aus:

css
select {
  border: 2px solid #ddd;
  border-radius: 8px;
  background: #eee;
  width: 200px;
  height: 130px;
}

option {
  background: #eee;
  padding: 10px;
  height: 40px;
  outline: none;
}

option:nth-of-type(odd) {
  background: #fff;
}

Als Nächstes setzen wir einen order-Wert von 1 auf das ::checkmark-Pseudoelement, um das Häkchen für ausgewählte Optionen rechts statt links erscheinen zu lassen, und setzen ein benutzerdefiniertes Häkchen-Symbol mit der content-Eigenschaft.

css
option::checkmark {
  order: 1;
  margin-left: auto;
  content: "☑️";
}

Schließlich setzen wir das font-weight für :checked-Optionen auf bold und eine benutzerdefinierte background-Farbe für die Zustände :hover und :focus, damit Sie immer wissen, welche Option Sie gehovt oder fokussiert haben.

css
option:checked {
  font-weight: bold;
}

option:hover,
option:focus {
  background: plum;
}

Dieses Beispiel wird wie folgt dargestellt:

Stilvariationen für Listenfelder

Da angepasste Listenfelder nur standardmäßige HTML-Elemente sind, können Sie sie nach Belieben gestalten. In diesem Abschnitt zeigen wir Ihnen einige Variationen des vorherigen Beispiels. Sie verwenden alle dasselbe oder ähnliches Markup; Wir haben ein bisschen zusätzliches CSS hinzugefügt, um das Aussehen und Gefühl deutlich zu ändern.

Erweitertes Listenfeld

In diesem Beispiel präsentieren wir das Listenfeld standardmäßig in der height einer einzelnen Option, verstecken den dadurch entstehenden overflow, und fügen eine transition hinzu, um die <select>-Höhe beim Zustandswechsel sanft zu animieren. Wir setzen auch einen interpolate-size-Wert von allow-keywords, um den Browser für das Animieren zwischen Längen und Schlüsselwörtern zu aktivieren.

css
select {
  height: 44px;
  overflow: hidden;
  transition: 0.6s height;
  interpolate-size: allow-keywords;
}

Wir ändern die height auf fit-content, wenn das <select> markiert oder fokussiert wird, damit es auf seine volle Höhe erweitert wird. Beachten Sie, dass beim Tabben in ein angepasstes Select das erste <option> den Fokus erhält, nicht das <select> selbst. Daher mussten wir select:has(option:focus) verwenden, um das <select> zu markieren, wenn eine <option> fokussiert ist, anstatt nur select:focus.

css
select:hover,
select:has(option:focus) {
  height: fit-content;
}

Beispiel wird jetzt wie folgt dargestellt:

Horizontales Listenfeld

In diesem Beispiel präsentieren wir die Listenfeldoptionen horizontal anstatt vertikal.

Das HTML ist das gleiche wie bei den vorherigen Beispielen, außer dass wir einen zusätzlichen Wrapper-<div> hinzugefügt haben, um uns zu ermöglichen, eine width auf das <select> zu setzen und dann eine andere width auf den Wrapper, so dass alle <option>-Elemente in einer Zeile gehalten werden und gescrollt werden können, wenn das <select> zu schmal wird, um sie alle aufzunehmen.

html
<p>
  <label for="pet-select">Select pets:</label><br />
  <select id="pet-select" multiple>
    <div class="wrapper">
      <option value="cat">Cat</option>
      <option value="dog">Dog</option>
      <option value="chicken">Chicken</option>
      <option value="fish">Fish</option>
      <option value="hamster">Hamster</option>
      <option value="gerbil">Gerbil</option>
      <option value="guinea">Guinea pig</option>
    </div>
  </select>
</p>

Im CSS beginnen wir damit, das enthaltene <p>-Element und seine width und margin so zu setzen, dass das Demo horizontal im Viewport zentriert und die meiste Breite einnimmt. Wir skalieren dann das <select>, damit es die volle Breite seines Elternteils einnimmt und nur so hoch ist wie die <option>-Elemente. Das .wrapper-<div> erhält einen display-Wert von flex, wodurch die <option>-Elemente horizontal in einer Reihe angeordnet werden; und wir setzen seine width, damit sie immer so breit ist wie die <option>-Elemente.

css
p {
  width: 90%;
  margin: 0 auto;
}

select {
  width: 100%;
  height: fit-content;
}

.wrapper {
  display: flex;
  width: fit-content;
}

Als Nächstes geben wir den <option>-Elementen zusätzliche Polsterung, um sie horizontal zu verteilen, und einen relativen position-Wert, damit wir ihre Nachkommen relativ zu ihnen positionieren können.

css
option {
  padding: 10px 30px;
  position: relative;
}

Zum Schluss positionieren wir die Optionskontrollkästchen absolut und geben ihnen ein benutzerdefiniertes Aussehen.

css
option::checkmark {
  position: absolute;
  top: -2px;
  left: 2px;
  font-size: 1.5rem;
  color: red;
  text-shadow: 1px 1px 1px black;
}

Unsere zweite Variation wird wie folgt dargestellt:

Ein komplexeres Listenfeld

In diesem Abschnitt gehen wir durch ein komplexeres Beispiel, das ein Kontakt-Auswahl-Listenfeld mit integriertem Filterfeld und einem Link zum Zugriff auf einen (fiktiven) Kontaktbearbeitungsmodus bereitstellt.

HTML

Im Markup fügen wir ein <form> ein, das eine Überschrift und ein Wrapper-<div> enthält. Im Wrapper fügen wir drei weitere <div>-Elemente ein, die jeweils ein Text<input>, das unser Filterfeld darstellt, ein Listenfeld-<select>, und einen Link enthalten. Das <select> wird durch JavaScript mit <option>-Elementen gefüllt, die unsere Kontaktoptionen darstellen.

html
<form>
  <h2>Contact select</h2>
  <div class="wrapper">
    <div class="filter">
      <input
        type="text"
        aria-label="Filter contacts"
        placeholder="Filter by name, e.g. amara" />
    </div>
    <div class="options">
      <select
        multiple
        name="contact-select"
        aria-label="Select contacts"></select>
    </div>
    <div class="edit">
      <a href="#">Edit contacts</a>
    </div>
  </div>
</form>

CSS

Wir beginnen unser CSS, indem wir das <select>-Element wie zuvor in benutzerdefinierte Stile übertragen:

css
select {
  appearance: base-select;
}

Der Großteil des Stylings ist ziemlich rudimentär, aber wir gehen es durch und heben dabei alles Wesentliche hervor. Zuerst stylen wir das .wrapper-<div>, dem wir eine feste width geben, die die horizontale Größe des gesamten Steuerelements steuert.

css
.wrapper {
  border: 2px solid #ddd;
  border-radius: 8px;
  background: #ddd;
  width: 250px;
}

Als Nächstes stylen wir das Filter-<input>, das .options-<div> und das enthaltene <select>, sowie das .edit-<div> mit dem Link. Am bemerkenswertesten ist, dass wir dem <select> eine feste height und einen overflow-y-Wert von scroll geben, damit die enthaltenen <option>-Elemente darin scrollen.

css
.filter input {
  display: block;
  padding: 5px;
  border-radius: 5px;
  border: 1px solid #bbb;
  width: 95%;
  margin: 8px auto;
}

.options {
  padding: 0 5px;
  background: #ddd;
}

select {
  height: 200px;
  overflow-y: scroll;
  width: 100%;
  border: 1px solid #bbb;
}

.edit {
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
}

Wir gestalten unsere <option>-Elemente in ähnlicher Weise wie in früheren Beispielen, indem wir ihnen ein Streifenmuster sowie klare :hover- und :focus-Stile geben:

css
option {
  background: #eee;
  padding: 10px;
}

option:nth-of-type(odd) {
  background: #fff;
}

option:checked {
  font-weight: bold;
}

option:hover,
option:focus {
  background: plum;
}

Unser nächster Schritt besteht darin, den Standard-Fokusbereich für die <input>-, <option>- und <a>-Elemente zu entfernen. Wir haben in den vorherigen Codeblöcken bereits eine alternative Gestaltung für die <option>-Elemente bereitgestellt; Hier bieten wir subtilere Alternativen für die <input>- und <a>-Elemente.

css
input,
option,
a {
  outline: none;
}

input:hover,
input:focus {
  border: 1px solid #999;
  background: #eef;
}

.edit a {
  color: #333;
}

a:hover,
a:focus {
  outline: 2px dotted #666;
}

Schließlich bieten wir benutzerdefiniertes Styling für die Kontrollkästchen der ausgewählten Optionen über das ::checkmark-Pseudoelement:

css
option::checkmark {
  order: 1;
  margin-left: auto;
  content: "☑️";
}

JavaScript

Die letzte Ergänzung, die unser Beispiel benötigt, ist ein wenig JavaScript, um die Optionserstellungs- und Filterfunktionalität zu ermöglichen.

Auf einer echten Website ziehen Sie wahrscheinlich eine aktuelle Kontaktliste von einem Server ab, aber in diesem Fall haben wir die Daten in einem statischen contacts-Objekt bereitgestellt (wir haben die meisten Kontakte der Kürze halber ausgeblendet). Für jeden Kontakt speichern wir einen Namen und ein boolean, das angibt, ob sie im <select>-Element ausgewählt waren.

js
const contacts = [
  { name: "Aisha Khan", selected: false },
  ...
];

Wir beginnen damit, Verweise auf unsere .filter-<input>- und <select>-Elemente abzurufen:

js
const filterInput = document.querySelector(".filter input");
const select = document.querySelector("select");

Als Nächstes definieren wir eine Funktion namens populateOptions(), die ein Array von Objekten als Parameter nimmt. Innerhalb der Funktion leeren wir zuerst den Inhalt des <select>-Elements. Dann durchlaufen wir das Eingabearray und erstellen ein <option>-Element für jedes Objekt im Array, indem wir seine textContent- und selected-Eigenschaften auf die name- und selected-Eigenschaften des Objekts setzen. Jedes <option>-Element wird als Kind des <select> in das DOM eingefügt.

js
function populateOptions(array) {
  select.innerHTML = "";

  array.forEach((obj) => {
    const option = document.createElement("option");
    option.textContent = obj.name;
    option.selected = obj.selected;
    select.appendChild(option);
  });
}

Nun definieren wir eine weitere Funktion, filterOptions(), die einen Filterstring und ein Array von Objekten als Parameter nimmt. Wir überprüfen, ob der String gleich dem leeren String oder einem oder mehreren Leerzeichen ist, indem wir den Rückgabewert seiner trim()-Methode mit "" vergleichen. Wenn dies true zurückgibt, führen wir die Funktion populateOptions() aus, übergeben ihr das vollständige Array, sodass das <select> mit allen <option>-Elementen gefüllt wird. Wenn es false zurückgibt, filtern wir das Eingabearray mit seiner filter()-Methode, um nur Objekte einzuschließen, deren name-Eigenschaft den filter-String startsWith() hat, dann übergeben wir das gefilterte Array an die populateOptions()-Funktion, damit das <select> mit einem gefilterten Satz <option>-Elemente gefüllt wird.

js
function filterOptions(filter, array) {
  if (filter.trim() === "") {
    populateOptions(array);
  } else {
    const filteredArray = array.filter((obj) =>
      obj.name.toLowerCase().startsWith(filter.toLowerCase()),
    );
    populateOptions(filteredArray);
  }
}

Hinweis: Wir konvertieren sowohl den Objekt-name als auch den filter-String mit toLowerCase() in Kleinbuchstaben, sodass die Filterübereinstimmung nicht zwischen Groß- und Kleinschreibung unterscheidet.

Als Nächstes fügen wir dem .filter-<input>-Element einen input-Ereignislistener hinzu, sodass beim Bearbeiten seines Werts die filterOptions()-Funktion ausgeführt wird, um die angezeigten <option>-Elemente zu filtern. Wir übergeben ihm den aktuellen Wert des <input> als Filterstring und das contacts-Array als Eingabearray.

js
filterInput.addEventListener("input", () => {
  filterOptions(filterInput.value, contacts);
});

Im nächsten Codeabschnitt wird dem <select>-Element ein change-Ereignislistener hinzugefügt, sodass jedes Mal, wenn ein <option> ausgewählt oder abgewählt wird, der selected-Status der Objekte im contacts-Array mit dem ausgewählten Status der aktuell angezeigten <option>-Objekte synchronisiert wird. Dies ist erforderlich, da jedes Mal, wenn wir einen neuen Filter auf unser <select>-Element anwenden, die angezeigten <option>-Elemente frisch aus dem contacts-Array generiert werden, welches deren ausgewählten Status umfasst. Wenn wir dies nicht tun würden, würden wir bei jedem Filterwechsel unsere ausgewählten Optionen verlieren.

Es gibt keine Möglichkeit, genau zu erkennen, welches <option> sich jedes Mal geändert hat, wenn eins umgeschaltet wird, also haben wir das Problem so gelöst:

  1. Holen Sie ein Array aller derzeit angezeigten <option>-Werte, indem Sie ein Array aus der select.options-Sammlung mit Array.from erstellen, und dann das Array mit seiner map()-Methode so zuzuordnen, dass jedes <option>-Element im Array durch seinen Wert ersetzt wird.
  2. Holen Sie ein Array aller aktuell ausgewählten <option>-Werte mit der gleichen Methodik, aber dieses Mal erstellen wir das Eingabearray aus der select.selectedOptions-Sammlung.
  3. Für jedes Kontaktobjekt im contacts-Array prüfen Sie, ob der Wert der name-Eigenschaft des Kontakts im allCurrentValues-Array enthalten ist, indem die includes()-Methode verwendet wird. Wenn nicht, ignorieren Sie es, damit wir nicht den ausgewählten Status der Kontakte umschalten, die überhaupt nicht angezeigt werden. Wenn ja, setzen Sie die selected-Eigenschaft des Kontakts auf das Ergebnis der Überprüfung, ob das currentSelectedValues-Array den Kontaktname includes() - wenn dies der Fall ist, setzen Sie die Objekt-Eigenschaft auf true, andernfalls auf false.
js
select.addEventListener("change", () => {
  const allCurrentValues = Array.from(select.options).map(
    (option) => option.value,
  );
  const currentSelectedValues = Array.from(select.selectedOptions).map(
    (option) => option.value,
  );

  contacts.forEach((contact) => {
    if (allCurrentValues.includes(contact.name)) {
      contact.selected = currentSelectedValues.includes(contact.name);
    }
  });
});

Schließlich führen wir die populateOptions()-Funktion aus und übergeben ihr das contacts-Array, sodass beim Laden der Seite die vollständige Liste der Kontakte angezeigt wird.

js
populateOptions(contacts);

Ergebnis

Das Beispiel rendert wie folgt:

Als Nächstes

Im nächsten Artikel dieses Moduls werden wir die verschiedenen UI-Pseudoklassen untersuchen, die wir in modernen Browsern zur Gestaltung von Formularen in verschiedenen Zuständen verwenden können.

Siehe auch