Switch-Komponente erstellen

Eine grundlegende Übersicht darüber, wie Sie eine responsive und barrierefreie Schalterkomponente erstellen.

In diesem Beitrag möchte ich meine Überlegungen zur Entwicklung von Schalterkomponenten teilen. Demo ansehen.

Demo

Wenn du lieber ein Video ansehen möchtest, findest du hier eine YouTube-Version dieses Beitrags:

Übersicht

Ein Schalter funktioniert ähnlich wie ein Kästchen zum Ankreuzen, stellt aber explizit die booleschen Status „Ein“ und „Aus“ dar.

In dieser Demo wird <input type="checkbox" role="switch"> für die meisten Funktionen verwendet. Das hat den Vorteil, dass kein CSS oder JavaScript erforderlich ist, damit die Funktionen vollständig und barrierefrei sind. Das Laden von CSS bietet Unterstützung für Sprachen, die von rechts nach links geschrieben werden, für vertikale Ausrichtung, Animationen und vieles mehr. Durch das Laden von JavaScript wird der Schalter interaktiv und kann gezogen werden.

Benutzerdefinierte Eigenschaften

Die folgenden Variablen stellen die verschiedenen Teile des Schalters und ihre Optionen dar. Als Klasse auf oberster Ebene enthält .gui-switch benutzerdefinierte Eigenschaften, die in den untergeordneten Komponenten verwendet werden, sowie Einstiegspunkte für die zentrale Anpassung.

Verfolgen

Die Länge (--track-size), das Padding und zwei Farben:

.gui-switch {
  --track-size: calc(var(--thumb-size) * 2);
  --track-padding: 2px;

  --track-inactive: hsl(80 0% 80%);
  --track-active: hsl(80 60% 45%);

  --track-color-inactive: var(--track-inactive);
  --track-color-active: var(--track-active);

  @media (prefers-color-scheme: dark) {
    --track-inactive: hsl(80 0% 35%);
    --track-active: hsl(80 60% 60%);
  }
}

Thumbnails

Die Größe, Hintergrundfarbe und Farben für die Interaktionshervorhebung:

.gui-switch {
  --thumb-size: 2rem;
  --thumb: hsl(0 0% 100%);
  --thumb-highlight: hsl(0 0% 0% / 25%);

  --thumb-color: var(--thumb);
  --thumb-color-highlight: var(--thumb-highlight);

  @media (prefers-color-scheme: dark) {
    --thumb: hsl(0 0% 5%);
    --thumb-highlight: hsl(0 0% 100% / 25%);
  }
}

Weniger Bewegung

Um einen eindeutigen Alias hinzuzufügen und Wiederholungen zu vermeiden, kann eine Media-Query für Nutzer mit der Einstellung „Reduzierte Bewegung“ mit dem PostCSS-Plug-in in eine benutzerdefinierte Eigenschaft eingefügt werden. Grundlage dafür ist dieser Entwurf in Media Queries 5:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

Markieren & Zeichnen

Ich habe das <input type="checkbox" role="switch">-Element in ein <label>-Element eingeschlossen, um die Beziehung zwischen den beiden Elementen zu bündeln und Unklarheiten bei der Zuordnung von Checkbox und Label zu vermeiden. Gleichzeitig kann der Nutzer durch Interaktion mit dem Label die Eingabe ein- oder ausschalten.

Ein natürliches, nicht formatiertes Label und ein Kästchen.

<label for="switch" class="gui-switch">
  Label text
  <input type="checkbox" role="switch" id="switch">
</label>

<input type="checkbox"> wird mit einer API und einem Status ausgeliefert. Der Browser verwaltet das Attribut checked und Eingabeereignisse wie oninput und onchanged.

Layouts

Flexbox, Grid und benutzerdefinierte Eigenschaften sind entscheidend, um die Stile dieser Komponente beizubehalten. Sie zentralisieren Werte, geben ansonsten mehrdeutigen Berechnungen oder Bereichen Namen und ermöglichen eine kleine benutzerdefinierte Property-API für einfache Komponentenanpassungen.

.gui-switch

Das Layout auf oberster Ebene für den Schalter ist Flexbox. Die Klasse .gui-switch enthält die privaten und öffentlichen benutzerdefinierten Eigenschaften, die die untergeordneten Elemente zum Berechnen ihrer Layouts verwenden.

Flexbox-DevTools-Overlay mit einem horizontalen Label und Schalter, das die Verteilung des Layouts zeigt.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

Das Flexbox-Layout zu erweitern und zu ändern ist wie das Ändern eines beliebigen Flexbox-Layouts. Wenn Sie beispielsweise Labels über oder unter einem Schalter platzieren oder die flex-direction ändern möchten:

Flexbox-DevTools-Overlay mit einem vertikalen Label und Schalter.

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

Verfolgen

Die Checkbox-Eingabe wird als Schalter dargestellt, indem die normale appearance: checkbox entfernt und eine eigene Größe angegeben wird:

Die Grid DevTools überlagern den Switch-Track und zeigen die benannten Grid-Track-Bereiche mit dem Namen „track“ an.

.gui-switch > input {
  appearance: none;

  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  padding: var(--track-padding);

  flex-shrink: 0;
  display: grid;
  align-items: center;
  grid: [track] 1fr / [track] 1fr;
}

Außerdem wird ein Bereich für einen Thumb erstellt, der aus einer einzelnen Zelle besteht.

Thumbnails

Mit dem Stil appearance: none wird auch das vom Browser bereitgestellte visuelle Häkchen entfernt. Für diese Komponente wird ein Pseudoelement und die :checked-Pseudoklasse für die Eingabe verwendet, um diesen visuellen Hinweis zu ersetzen.

Der Thumb ist ein untergeordnetes Pseudo-Element, das an input[type="checkbox"] angehängt ist und über dem Track statt darunter gestapelt wird, indem der Grid-Bereich track beansprucht wird:

DevTools mit dem Pseudo-Element „thumb“, das in einem CSS-Raster positioniert ist.

.gui-switch > input::before {
  content: "";
  grid-area: track;
  inline-size: var(--thumb-size);
  block-size: var(--thumb-size);
}

Stile

Benutzerdefinierte Eigenschaften ermöglichen eine vielseitige Schalterkomponente, die sich an Farbschemas, Sprachen mit Leserichtung von rechts nach links und Bewegungseinstellungen anpasst.

Ein direkter Vergleich des hellen und dunklen Designs für den Schalter und seine Status.

Berührungsgesten

Auf Mobilgeräten fügen Browser Labels und Eingaben Tipp-Highlights und Textauswahlfunktionen hinzu. Dies wirkte sich negativ auf den Stil und das visuelle Interaktionsfeedback aus, das für diesen Schalter erforderlich war. Mit wenigen Zeilen CSS kann ich diese Effekte entfernen und meinen eigenen cursor: pointer-Stil hinzufügen:

.gui-switch {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

Es ist nicht immer ratsam, diese Stile zu entfernen, da sie wertvolles visuelles Feedback zur Interaktion liefern können. Wenn Sie sie entfernen, müssen Sie unbedingt benutzerdefinierte Alternativen angeben.

Verfolgen

Die Stile dieses Elements beziehen sich hauptsächlich auf seine Form und Farbe, auf die es über die Kaskade vom übergeordneten .gui-switch aus zugreift.

Die Schaltervarianten mit benutzerdefinierten Track-Größen und -Farben.

.gui-switch > input {
  appearance: none;
  border: none;
  outline-offset: 5px;
  box-sizing: content-box;

  padding: var(--track-padding);
  background: var(--track-color-inactive);
  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  border-radius: var(--track-size);
}

Die zahlreichen Anpassungsoptionen für den Switch-Track ergeben sich aus vier benutzerdefinierten Eigenschaften. border: none wird hinzugefügt, da appearance: none die Rahmen des Kästchens nicht in allen Browsern entfernt.

Thumbnails

Das Thumb-Element befindet sich bereits auf der rechten Seite track, benötigt aber Kreisstile:

.gui-switch > input::before {
  background: var(--thumb-color);
  border-radius: 50%;
}

DevTools mit dem hervorgehobenen Pseudo-Element „circle thumb“

Interaktion

Mit benutzerdefinierten Eigenschaften können Sie sich auf Interaktionen vorbereiten, bei denen Hover-Hervorhebungen und Änderungen der Daumenposition angezeigt werden. Die Einstellung des Nutzers wird ebenfalls geprüft, bevor die Stile für Bewegungs- oder Hover-Highlights geändert werden.

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

Position des Daumens

Benutzerdefinierte Eigenschaften bieten einen Mechanismus, um die Position des Thumb im Track festzulegen. Wir haben die Größen für den Track und den Thumb zur Verfügung, die wir für Berechnungen verwenden, um den Thumb richtig zu versetzen und innerhalb des Tracks zu halten: 0% und 100%.

Das input-Element enthält die Positionsvariable --thumb-position und das Pseudo-Element „thumb“ verwendet sie als translateX-Position:

.gui-switch > input {
  --thumb-position: 0%;
}

.gui-switch > input::before {
  transform: translateX(var(--thumb-position));
}

Wir können --thumb-position jetzt über CSS und die Pseudoklassen ändern, die für Checkbox-Elemente bereitgestellt werden. Da wir transition: transform var(--thumb-transition-duration) ease für dieses Element zuvor bedingt festgelegt haben, können diese Änderungen animiert werden, wenn sie geändert werden:

/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
}

/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
}

Ich fand diese entkoppelte Orchestrierung gut. Das „thumb“-Element bezieht sich nur auf einen Stil, eine translateX-Position. Die Eingabe kann die gesamte Komplexität und alle Berechnungen bewältigen.

Branche

Die Unterstützung erfolgte mit der Modifikator-Klasse -vertical, die dem input-Element eine Drehung mit CSS-Transformationen hinzufügt.

Die Gesamthöhe der Komponente ändert sich jedoch nicht, was sich auf das Blocklayout auswirken kann. Berücksichtigen Sie dies mit den Variablen --track-size und --track-padding. Berechnen Sie den Mindestplatz, der für eine vertikale Schaltfläche erforderlich ist, damit sie wie erwartet im Layout dargestellt wird:

.gui-switch.-vertical {
  min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));

  & > input {
    transform: rotate(-90deg);
  }
}

(RTL) von rechts nach links

Ein CSS-Freund, Elad Schecter, und ich haben gemeinsam ein mit CSS-Transformationen realisiertes seitliches Einblendmenü prototypisiert, das durch Umkehren einer einzelnen Variablen auch für Sprachen mit Leserichtung von rechts nach links geeignet ist. Das liegt daran, dass es in CSS keine logischen Property-Transformationen gibt und es sie möglicherweise auch nie geben wird. Elad hatte die tolle Idee, einen benutzerdefinierten Attributwert zu verwenden, um Prozentsätze umzukehren. So können wir unsere eigene benutzerdefinierte Logik für logische Transformationen an einem einzigen Ort verwalten. Ich habe diese Technik auch bei diesem Schalter verwendet und finde, dass sie gut funktioniert hat:

.gui-switch {
  --isLTR: 1;

  &:dir(rtl) {
    --isLTR: -1;
  }
}

Eine benutzerdefinierte Eigenschaft namens --isLTR hat anfangs den Wert 1. Das bedeutet, sie ist true, da unser Layout standardmäßig von links nach rechts ausgerichtet ist. Anschließend wird der Wert mit der CSS-Pseudoklasse :dir() auf -1 gesetzt, wenn sich die Komponente in einem Layout mit Leserichtung von rechts nach links befindet.

Setzen Sie --isLTR in die Praxis um, indem Sie sie in einer calc() innerhalb einer Transformation verwenden:

.gui-switch.-vertical > input {
  transform: rotate(-90deg);
  transform: rotate(calc(90deg * var(--isLTR) * -1));
}

Die Drehung des vertikalen Schalters berücksichtigt nun die gegenüberliegende Seitenposition, die für das Rechts-nach-links-Layout erforderlich ist.

Die translateX-Transformationen für das Pseudo-Element „thumb“ müssen ebenfalls aktualisiert werden, um die Anforderung für die gegenüberliegende Seite zu berücksichtigen:

.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
  --thumb-position: calc(
   ((var(--track-size) / 2) - (var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

Dieser Ansatz ist zwar nicht für alle Anforderungen an ein Konzept wie logische CSS-Transformationen geeignet, bietet aber für viele Anwendungsfälle einige DRY-Prinzipien.

Bundesstaaten

Die integrierte input[type="checkbox"] wäre nicht vollständig, ohne die verschiedenen Zustände zu berücksichtigen, in denen sie sich befinden kann: :checked, :disabled, :indeterminate und :hover. :focus wurde bewusst nicht verändert, sondern nur der Offset angepasst. Der Fokusring sah in Firefox und Safari gut aus:

Ein Screenshot des Fokusrings, der auf einen Schalter in Firefox und Safari fokussiert ist.

Geprüft

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

Dieser Status entspricht dem Status on. In diesem Zustand wird der Hintergrund des Eingabe-Tracks auf die aktive Farbe und die Position des Thumb auf „Ende“ festgelegt.

.gui-switch > input:checked {
  background: var(--track-color-active);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

Deaktiviert

<label for="switch-disabled" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>

Eine :disabled-Schaltfläche sieht nicht nur anders aus, sondern sollte das Element auch unveränderlich machen.Die Unveränderlichkeit der Interaktion ist im Browser kostenlos, aber für die visuellen Status sind aufgrund der Verwendung von appearance: none Stile erforderlich.

.gui-switch > input:disabled {
  cursor: not-allowed;
  --thumb-color: transparent;

  &::before {
    cursor: not-allowed;
    box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);

    @media (prefers-color-scheme: dark) { & {
      box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
    }}
  }
}

Der Schalter im dunklen Design in den Status „Deaktiviert“, „Aktiviert“ und „Nicht aktiviert“.

Dieser Status ist schwierig, da er dunkle und helle Designs mit deaktivierten und aktivierten Status erfordert. Ich habe für diese Status stilistisch minimale Formatierungen gewählt, um den Wartungsaufwand für die Kombinationen von Formatierungen zu verringern.

Unklar

Ein oft vergessener Status ist :indeterminate, in dem ein Kästchen weder aktiviert noch deaktiviert ist. Das ist ein angenehmer Zustand, der einladend und unaufdringlich wirkt. Eine gute Erinnerung daran, dass boolesche Zustände heimtückische Zwischenzustände haben können.

Es ist schwierig, ein Kontrollkästchen auf „Unbestimmt“ zu setzen. Das ist nur mit JavaScript möglich:

<label for="switch-indeterminate" class="gui-switch">
  Indeterminate
  <input type="checkbox" role="switch" id="switch-indeterminate">
  <script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>

Der unbestimmte Status, bei dem sich der Track-Thumb in der Mitte befindet, um anzuzeigen, dass noch keine Entscheidung getroffen wurde.

Da der Bundesstaat für mich bescheiden und einladend ist, erschien es mir angemessen, die Daumenposition des Schalters in die Mitte zu setzen:

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

Mauszeiger hierher bewegen

Bei Hover-Interaktionen sollte die verbundene Benutzeroberfläche visuell unterstützt und auf interaktive Elemente hingewiesen werden. Mit diesem Schalter wird der Thumb mit einem halbtransparenten Ring hervorgehoben, wenn der Mauszeiger auf das Label oder das Eingabefeld bewegt wird. Diese Hover-Animation weist dann auf das interaktive Thumb-Element hin.

Der Effekt „Hervorheben“ wird mit box-shadow erzielt. Erhöhen Sie beim Hovern über eine nicht deaktivierte Eingabe die Größe von --highlight-size. Wenn der Nutzer mit Bewegung einverstanden ist, wird box-shadow eingeblendet und wächst. Wenn er nicht mit Bewegung einverstanden ist, wird das Highlight sofort angezeigt:

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

.gui-switch > input:not(:disabled):hover::before {
  --highlight-size: .5rem;
}

JavaScript

Für mich kann sich eine Schalteroberfläche in ihrem Versuch, eine physische Oberfläche zu emulieren, unheimlich anfühlen, insbesondere diese Art mit einem Kreis in einer Spur. iOS hat das mit seinem Schalter richtig gemacht. Sie können ihn hin und her ziehen, und es ist sehr befriedigend, diese Option zu haben. Umgekehrt kann sich ein UI-Element inaktiv anfühlen, wenn versucht wird, es zu ziehen, und nichts passiert.

Ziehbare Thumbnails

Das Pseudo-Element „thumb“ erhält seine Position aus dem .gui-switch > input-Bereich var(--thumb-position). Mit JavaScript kann ein Inline-Stilwert für die Eingabe bereitgestellt werden, um die Position des Thumb dynamisch zu aktualisieren. So sieht es so aus, als würde der Thumb der Zeigerbewegung folgen. Wenn der Zeiger losgelassen wird, werden die Inline-Stile entfernt und mit der benutzerdefinierten Eigenschaft --thumb-position wird ermittelt, ob der Drag näher an „Aus“ oder „Ein“ war. Dies ist das Fundament der Lösung: Zeigerereignisse, die Zeigerpositionen bedingt erfassen, um benutzerdefinierte CSS-Properties zu ändern.

Da die Komponente bereits vor dem Erscheinen dieses Skripts zu 100% funktionsfähig war, ist es mit einigem Aufwand verbunden, das vorhandene Verhalten beizubehalten, z. B. das Umschalten der Eingabe durch Klicken auf ein Label. Unser JavaScript sollte keine Funktionen auf Kosten vorhandener Funktionen hinzufügen.

touch-action

Das Ziehen ist eine benutzerdefinierte Geste und eignet sich daher gut für die touch-action-Vorteile. Bei diesem Switch sollte eine horizontale Geste von unserem Skript verarbeitet oder eine vertikale Geste für die vertikale Switch-Variante erfasst werden. Mit touch-action können wir dem Browser mitteilen, welche Gesten für dieses Element verarbeitet werden sollen, damit ein Skript eine Geste ohne Konflikte verarbeiten kann.

Das folgende CSS weist den Browser an, vertikale Gesten zu verarbeiten, wenn eine Zeigergeste innerhalb dieser Schalttfläche beginnt, und horizontale Gesten zu ignorieren:

.gui-switch > input {
  touch-action: pan-y;
}

Das gewünschte Ergebnis ist eine horizontale Geste, mit der die Seite nicht auch geschwenkt oder gescrollt wird. Ein Zeiger kann vertikal innerhalb der Eingabe gescrollt werden und die Seite scrollen, horizontale Zeiger werden jedoch benutzerdefiniert behandelt.

Hilfsklassen für Pixelwerte

Bei der Einrichtung und während des Ziehens müssen verschiedene berechnete Zahlenwerte aus Elementen abgerufen werden. Die folgenden JavaScript-Funktionen geben berechnete Pixelwerte für eine bestimmte CSS-Eigenschaft zurück. Es wird im Einrichtungs-Script so verwendet: getStyle(checkbox, 'padding-left').

​​const getStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}

const getPseudoStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}

export {
  getStyle,
  getPseudoStyle,
}

Beachten Sie, dass window.getComputedStyle() ein zweites Argument akzeptiert, ein Pseudo-Zielelement. Es ist ziemlich praktisch, dass JavaScript so viele Werte aus Elementen lesen kann, sogar aus Pseudoelementen.

dragging

Dies ist ein wichtiger Moment für die Drag-Logik. Es gibt einige Dinge, die im Funktionsereignishandler zu beachten sind:

const dragging = event => {
  if (!state.activethumb) return

  let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
  let directionality = getStyle(state.activethumb, '--isLTR')

  let track = (directionality === -1)
    ? (state.activethumb.clientWidth * -1) + thumbsize + padding
    : 0

  let pos = Math.round(event.offsetX - thumbsize / 2)

  if (pos < bounds.lower) pos = 0
  if (pos > bounds.upper) pos = bounds.upper

  state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}

Der Script-Hero ist state.activethumb, der kleine Kreis, der mit einem Zeiger positioniert wird. Das switches-Objekt ist ein Map(), in dem die Schlüssel .gui-switch sind und die Werte zwischengespeicherte Grenzen und Größen, die das Skript effizient halten. Die Verarbeitung von RTL erfolgt über dieselbe benutzerdefinierte Eigenschaft, die CSS --isLTR verwendet. So kann die Logik umgekehrt und RTL weiterhin unterstützt werden. Der event.offsetX ist ebenfalls nützlich, da er einen Deltawert enthält, der für die Positionierung des Thumbnails verwendet werden kann.

state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)

Mit dieser letzten CSS-Zeile wird die benutzerdefinierte Property für das Thumb-Element festgelegt. Diese Zuweisung würde sich ansonsten im Laufe der Zeit ändern, aber ein vorheriges Zeigerereignis hat --thumb-transition-duration vorübergehend auf 0s gesetzt und so eine träge Interaktion verhindert.

dragEnd

Damit der Nutzer den Schalter weit nach außen ziehen und loslassen kann, muss ein globales Fensterereignis registriert werden:

window.addEventListener('pointerup', event => {
  if (!state.activethumb) return

  dragEnd(event)
})

Ich finde es sehr wichtig, dass Nutzer die Freiheit haben, das Tool locker zu bedienen, und dass die Benutzeroberfläche intelligent genug ist, um das zu berücksichtigen. Mit diesem Schalter war es nicht viel Aufwand, das Problem zu beheben, aber es musste während der Entwicklung sorgfältig berücksichtigt werden.

const dragEnd = event => {
  if (!state.activethumb) return

  state.activethumb.checked = determineChecked()

  if (state.activethumb.indeterminate)
    state.activethumb.indeterminate = false

  state.activethumb.style.removeProperty('--thumb-transition-duration')
  state.activethumb.style.removeProperty('--thumb-position')
  state.activethumb.removeEventListener('pointermove', dragging)
  state.activethumb = null

  padRelease()
}

Die Interaktion mit dem Element ist abgeschlossen. Es ist an der Zeit, die Eigenschaft „input checked“ festzulegen und alle Gestenereignisse zu entfernen. Das Kästchen wird durch state.activethumb.checked = determineChecked() ersetzt.

determineChecked()

Diese Funktion, die von dragEnd aufgerufen wird, bestimmt, wo sich der Thumb innerhalb der Grenzen des Tracks befindet, und gibt „true“ zurück, wenn er sich auf der Hälfte des Tracks oder darüber befindet:

const determineChecked = () => {
  let {bounds} = switches.get(state.activethumb.parentElement)

  let curpos =
    Math.abs(
      parseInt(
        state.activethumb.style.getPropertyValue('--thumb-position')))

  if (!curpos) {
    curpos = state.activethumb.checked
      ? bounds.lower
      : bounds.upper
  }

  return curpos >= bounds.middle
}

Zusätzliche Überlegungen

Das Drag-and-drop-Verfahren hat aufgrund der ursprünglichen HTML-Struktur, insbesondere der Einbettung der Eingabe in ein Label, zu einem gewissen Code-Schulden geführt. Das Label als übergeordnetes Element würde nach der Eingabe Klickinteraktionen empfangen. Am Ende des dragEnd-Events ist Ihnen vielleicht padRelease() als seltsam klingende Funktion aufgefallen.

const padRelease = () => {
  state.recentlyDragged = true

  setTimeout(_ => {
    state.recentlyDragged = false
  }, 300)
}

So wird berücksichtigt, dass das Label diesen späteren Klick erhält, da dadurch die Interaktion eines Nutzers deaktiviert oder aktiviert wird.

Wenn ich das noch einmal machen würde, könnte ich in Erwägung ziehen, das DOM während des UX-Upgrades mit JavaScript anzupassen, um ein Element zu erstellen, das Labelklicks selbst verarbeitet und nicht mit dem integrierten Verhalten kollidiert.

Diese Art von JavaScript schreibe ich am wenigsten gern. Ich möchte kein bedingtes Event-Bubbling verwalten:

const preventBubbles = event => {
  if (state.recentlyDragged)
    event.preventDefault() && event.stopPropagation()
}

Fazit

Diese winzige Schalterkomponente hat bisher am meisten Arbeit von allen GUI-Herausforderungen verursacht. Jetzt wissen Sie, wie ich es gemacht habe. Wie würden Sie vorgehen? 🙂

Wir möchten unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, die das Web bietet. Erstelle eine Demo, schick mir einen Tweet mit den Links und ich füge sie unten im Bereich „Community-Remixe“ hinzu.

Community-Remixe

Ressourcen

Den .gui-switch-Quellcode finden Sie auf GitHub.