Switch-Komponente erstellen

Ein grundlegender Überblick darüber, wie Sie eine responsive und barrierefreie Switch-Komponente erstellen.

In diesem Beitrag möchte ich darüber sprechen, wie Switch-Komponenten aufgebaut werden können. Demo ansehen.

Demo

Falls du lieber ein Video hast, findest du hier eine YouTube-Version dieses Beitrags:

Überblick

Ein Switch funktioniert ähnlich wie ein Kästchen, stellt aber explizit einen booleschen Ein- und Aus-Zustand dar.

In dieser Demo wird für den Großteil der Funktionalität <input type="checkbox" role="switch"> verwendet. Dies hat den Vorteil, dass CSS oder JavaScript nicht erforderlich ist, um voll funktionsfähig und zugänglich zu sein. Wenn Sie CSS laden, werden nun auch Sprachen mit rechtsläufiger Schrift, Vertikalität, Animationen und mehr unterstützt. Durch das Laden von JavaScript wird der Schalter ziehbar und greifbar.

Benutzerdefinierte Eigenschaften

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

Verfolgen

Länge (--track-size), Abstand 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%);
  }
}

Daumenschlag

Größe, Hintergrundfarbe und Interaktionsfarben:

.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%);
  }
}

Reduzierte Bewegung

Um einen eindeutigen Alias hinzuzufügen und Wiederholungen zu reduzieren, kann eine Nutzermedienabfrage mit reduzierter Bewegungseinstellung mithilfe des PostCSS-Plug-ins in eine benutzerdefinierte Eigenschaft eingefügt werden, die auf dieser Spezifikationsentwurf in Medienabfragen 5 basiert:

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

Markup

Ich habe mich dafür entschieden, mein <input type="checkbox" role="switch">-Element mit einem <label> zu umschließen und die Beziehung zu bündeln, um Mehrdeutigkeiten bei Kästchen- und Labelverknüpfungen zu vermeiden und dem Nutzer gleichzeitig die Möglichkeit zu geben, mit dem Label zu interagieren, um die Eingabe umzuschalten.

Ein natürliches Label und ein Kästchen ohne Stil.

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

<input type="checkbox"> enthält eine API und einen state. Der Browser verwaltet das Attribut checked und die Eingabeereignisse wie oninput und onchanged.

Layouts

Flexbox-, grid- und benutzerdefinierte Eigenschaften sind entscheidend für die Verwaltung der Stile dieser Komponente. Sie zentralisieren Werte, geben ansonsten mehrdeutigen Berechnungen oder Bereichen Namen und ermöglichen eine kleine API für benutzerdefinierte Eigenschaften für eine einfache Anpassung von Komponenten.

.gui-switch

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

Flexbox-Entwicklertools, die ein horizontales Label und einen Schalter überlagern und die Flächenverteilung im Layout zeigen.

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

Das Erweitern und Ändern des Flexbox-Layouts ist wie das Ändern eines Flexbox-Layouts. So platzieren Sie beispielsweise Labels ober- oder unterhalb eines Switches oder ändern flex-direction:

Flexbox-Entwicklertools, die ein vertikales Label und einen Schalter überlagern.

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

Verfolgen

Die Kästcheneingabe wird als Schalterspur formatiert. Dabei wird das normale appearance: checkbox entfernt und stattdessen seine eigene Größe angegeben:

Raster-Entwicklertools, die die Schaltschiene überlagern und die benannten Rasterspurbereiche mit dem Namen „track“ zeigen.

.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;
}

Der Spur erstellt außerdem einen Rasterspurbereich mit einer einzelnen Zelle, den Sie per Daumen beanspruchen können.

Daumenschlag

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

Der Daumen ist ein untergeordnetes Pseudoelement, das an input[type="checkbox"] angehängt ist. Es wird über dem Track gestapelt und nicht darunter, indem er den Rasterbereich track beansprucht:

Entwicklertools mit dem Pseudoelement „Daumen“, 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 Farbschemata, linksläufige Sprachen und Bewegungseinstellungen anpasst.

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

Touch-Interaktionsstile

Auf Mobilgeräten fügen Browser den Labels und Eingaben Funktionen zum Hervorheben von Berührungen und Textauswahl hinzu. Dies wirkten sich negativ auf das Feedback zu Stil und visuellen Interaktionen aus, das für diesen Wechsel erforderlich war. Mit ein paar CSS-Zeilen 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 Interaktionsfeedback sein können. Achten Sie darauf, benutzerdefinierte Alternativen anzugeben, wenn Sie sie entfernen.

Verfolgen

Bei den Stilen dieses Elements geht es hauptsächlich um Form und Farbe, auf die es über die Kaskade vom übergeordneten .gui-switch aus zugreift.

Die Variante wechseln mit benutzerdefinierten Größen und Farben für die Tracks.

.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);
}

Aus vier benutzerdefinierten Eigenschaften stammen zahlreiche Anpassungsoptionen für die Schaltschiene. border: none wird hinzugefügt, da appearance: none die Rahmen nicht in allen Browsern aus dem Kästchen entfernt.

Daumenschlag

Das Daumenelement befindet sich bereits auf der rechten track, erfordert aber Kreisstile:

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

Abbildung der Entwicklertools mit Hervorhebung des Pseudoelements „Kreis“.

Interaktion

Mit benutzerdefinierten Eigenschaften können Sie sich auf Interaktionen vorbereiten, bei denen sich Hervorhebungen und Änderungen der Daumenposition ändern. Die Präferenz des Nutzers wird außerdem überprüft, bevor die Stile für Bewegungs- oder Hover-Hervorhebungen umgestellt 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;
  }}
}

Daumenposition

Benutzerdefinierte Eigenschaften bieten einen einzigen Quellmechanismus für die Positionierung des Daumens im Track. Wir haben Spur- und Daumengrößen zur Verfügung, die wir in Berechnungen verwenden, um für einen ausreichenden Abstand zwischen den Tracks innerhalb der Spur zu sorgen: 0% und 100%.

Das input-Element besitzt die Positionsvariable --thumb-position und das Thumbnail-Element verwendet diese als translateX-Position:

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

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

Wir können --thumb-position jetzt kostenlos von CSS und den Pseudoklassen für Kästchenelemente ändern. Da wir transition: transform var(--thumb-transition-duration) ease zuvor für dieses Element 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 dachte, diese entkoppelte Orchestrierung hat gut funktioniert. Das Daumenelement betrifft nur einen Stil, die translateX-Position. Die Eingabe kann die Komplexität und die Berechnungen verwalten.

Vertikal

Unterstützt wurde die Modifikatorklasse -vertical, die dem input-Element eine Rotation mit CSS-Transformationen hinzufügt.

Ein in 3D gedrehtes Element ändert jedoch nicht die Gesamthöhe der Komponente, was das Blocklayout beeinträchtigen kann. Berücksichtigen Sie dies mit den Variablen --track-size und --track-padding. Berechnen Sie den erforderlichen Mindestabstand, damit eine vertikale Schaltfläche wie erwartet im Layout fließt:

.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 zusammen ein ausblendbares seitliches Menü mit CSS-Transformationen entwickelt, mit denen durch Umdrehen einer einzelnen Variablen Sprachen von rechts nach links verarbeitet werden. Zu diesem Zweck gibt es in CSS keine logischen Eigenschaftstransformationen. Elad hatte die Idee, einen benutzerdefinierten Eigenschaftswert zu verwenden, um Prozentsätze umzukehren, um die Verwaltung eines einzigen Standorts unserer eigenen benutzerdefinierten Logik für logische Transformationen zu ermöglichen. Ich habe dieselbe Technik beim Wechsel verwendet und denke, es hat super funktioniert:

.gui-switch {
  --isLTR: 1;

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

Eine benutzerdefinierte Eigenschaft namens --isLTR enthält anfänglich den Wert 1, d. h. true, da unser Layout standardmäßig von links nach rechts verwendet wird. Dann wird mithilfe der CSS-Pseudoklasse :dir() der Wert auf -1 gesetzt, wenn sich die Komponente in einem linksläufigen Layout befindet.

Setzen Sie --isLTR in Aktion, indem Sie es innerhalb einer calc() innerhalb einer Transformation verwenden:

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

Jetzt berücksichtigt die Drehung des vertikalen Schalters die Position gegenüberliegender Seite, die für das Layout von rechts nach links erforderlich ist.

Die translateX-Transformationen auf dem Thumbnail-Element müssen ebenfalls aktualisiert werden, um die Anforderung der gegenüberliegenden 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 eignet sich zwar nicht für alle Anforderungen an ein Konzept wie logische CSS-Transformationen, bietet aber einige DRY-Prinzipien für viele Anwendungsfälle.

Bundesstaaten

Die Verwendung des integrierten input[type="checkbox"] wäre nicht vollständig, ohne die verschiedenen Status zu verarbeiten, in denen es sich befinden kann: :checked, :disabled, :indeterminate und :hover. :focus wurde absichtlich unverändert gelassen und nur an seinem Versatz angepasst. Der Fokusring sah in Firefox und Safari toll aus:

Screenshot des Fokusrings auf einem Schalter in Firefox und Safari

Geprüft

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

Dieser Status stellt den Status on dar. In diesem Status wird für den Eingabehintergrund die aktive Farbe und für die Daumenposition „das 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 optisch anders aus, sondern sollte auch dafür sorgen, dass das Element unveränderlich wird. Die Unveränderlichkeit der Interaktion ist vom Browser nicht möglich. Für den visuellen Status sind jedoch 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 dunkle Schalter im deaktivierten, aktivierten und deaktivierten Zustand

Dieser Status ist schwierig, da er dunkles und helles Design mit deaktiviertem und aktiviertem Status benötigt. Für diese Stadien habe ich stilistisch minimale Stile gewählt, um die Verwaltung von Stilkombinationen zu vereinfachen.

Unklar

Ein häufig vergessener Status ist :indeterminate, bei dem ein Kästchen weder aktiviert noch deaktiviert ist. Dieser Zustand ist unterhaltsam, einladend und unscheinbar. Denken Sie daran, dass boolesche Status heimlich zwischen Bundesstaaten erscheinen können.

Es ist schwierig, ein unbestimmtes Kontrollkästchen zu setzen, es kann nur von JavaScript gesetzt werden:

<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 Zustand mit dem Spursymbol in der Mitte, um eine Unentschlossenheit anzuzeigen.

Da der Bundesstaat für mich einfach und einladend ist, erschien es mir angebracht, die Position des Schaltknaufs in der Mitte zu platzieren:

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

Mauszeiger bewegen

Hover-Interaktionen sollten die verbundene UI visuell unterstützen und auch eine Richtung zur interaktiven UI vorgeben. Bei diesem Schalter wird der Daumen mit einem halbtransparenten Ring hervorgehoben, wenn der Mauszeiger auf Label oder Eingabe bewegt wird. Diese Hover-Animation zeigt dann die Richtung des interaktiven Daumenelements an.

Der „Hervorhebungseffekt“ wird mit box-shadow ausgeführt. Wenn Sie den Mauszeiger auf eine nicht deaktivierte Eingabe bewegen, wird die Größe von --highlight-size erhöht. Wenn der Nutzer mit Bewegungen einverstanden ist, übertragen wir box-shadow und sehen, wie er größer wird. Wenn Bewegungen mit Bewegungen nicht einverstanden sind, wird die Hervorhebung 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

Ich finde, eine Switch-Oberfläche kann einen etwas ungemütlichen Versuch anfühlen, eine physische Schnittstelle zu emulieren, insbesondere diese Art mit einem Kreis innerhalb einer Spur. iOS hat das mit dem Schalter richtig gemacht, man kann sie von einer Seite zur anderen ziehen und die Option ist sehr angenehm zu nutzen. Umgekehrt kann sich ein UI-Element als inaktiv anfühlen, wenn eine Ziehgeste versucht wird und nichts passiert.

Ziehbare Daumen

Das Pseudoelement „Daumen“ empfängt seine Position vom .gui-switch > input-Bereich var(--thumb-position). JavaScript kann einen Wert für den Inline-Style in der Eingabe bereitstellen, um die Daumenposition dynamisch zu aktualisieren, sodass es so aussieht, als würde es der Zeigergeste folgen. Wenn der Mauszeiger freigegeben wird, entfernen Sie die Inline-Stile und stellen Sie mithilfe der benutzerdefinierten Eigenschaft --thumb-position fest, ob der Ziehpunkt näher an der Position liegt. Dies ist das Herzstück der Lösung: Zeigerereignisse, die Zeigerpositionen bedingt verfolgen, um benutzerdefinierte CSS-Eigenschaften zu ändern.

Da die Komponente bereits zu 100% funktionsfähig war, bevor dieses Skript angezeigt wurde, ist es aufwendig, das bestehende Verhalten beizubehalten, z. B. das Anklicken eines Labels zum Umschalten der Eingabe. Unser JavaScript-Code sollte keine Funktionen auf Kosten vorhandener Funktionen hinzufügen.

touch-action

Das Ziehen ist eine benutzerdefinierte Geste, die besonders für touch-action-Vorteile geeignet ist. In diesem Fall sollte unser Skript eine horizontale Geste ausführen oder eine vertikale Geste für die vertikale Schaltervariante. Mit touch-action können wir dem Browser mitteilen, welche Gesten dieses Element verarbeiten soll, sodass ein Skript eine Geste ohne Konkurrenz verarbeiten kann.

Mit dem folgenden CSS wird der Browser angewiesen, vertikale Touch-Gesten, die innerhalb dieser Schalterspur beginnen, nicht mit horizontalen Gesten zu verarbeiten:

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

Das gewünschte Ergebnis ist eine horizontale Geste, bei der die Seite nicht geschwenkt oder gescrollt wird. Mit einem Zeiger kann von der Eingabe aus ein vertikales Scrollen gestartet und die Seite gescrollt werden. Horizontale können jedoch angepasst werden.

Dienstprogramme im Pixel-Stil

Bei der Einrichtung und während des Ziehens müssen verschiedene berechnete Zahlenwerte von Elementen abgerufen werden. Die folgenden JavaScript-Funktionen geben bei einer CSS-Eigenschaft berechnete Pixelwerte zurück. Sie wird im Einrichtungsskript wie hier 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 Pseudoelement. Sehr praktisch, dass JavaScript so viele Werte aus Elementen lesen kann, sogar aus Pseudoelementen.

dragging

Dies ist ein zentraler Moment für die Drag-Logik und es gibt einige Dinge aus dem Funktions-Event-Handler zu beachten:

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 Skript-Hero ist state.activethumb, der kleine Kreis, den dieses Skript zusammen mit einem Mauszeiger positioniert. Das switches-Objekt ist ein Map()-Objekt, bei dem die Schlüssel .gui-switch sind und die Werte im Cache gespeicherte Grenzen und Größen enthalten, damit das Skript effizient bleibt. Die Eingabe von rechts nach links wird mit derselben benutzerdefinierten Eigenschaft wie CSS --isLTR verarbeitet. Damit kann Logik invertiert werden, sodass weiterhin RTL unterstützt wird. event.offsetX ist ebenfalls hilfreich, da er einen Deltawert enthält, der für die Positionierung des Daumens nützlich ist.

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

In dieser letzten CSS-Zeile wird die benutzerdefinierte Eigenschaft festgelegt, die vom Element „thum“ verwendet wird. Ansonsten würde diese Wertzuweisung im Laufe der Zeit übergehen, aber durch ein früheres Zeigerereignis wurde --thumb-transition-duration vorübergehend auf 0s gesetzt, wodurch die bisher langsame Interaktion entfernt wurde.

dragEnd

Damit der Nutzer weit über den Schalter hinaus ziehen und ihn wieder loslassen kann, muss ein globales Fensterereignis registriert werden:

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

  dragEnd(event)
})

Ich denke, es ist sehr wichtig, dass der Nutzer die Freiheit hat, sich locker zu bewegen und die Benutzeroberfläche so intelligent zu gestalten, dass er dies berücksichtigt. Es war nicht viel für die Handhabung dieses Switches erforderlich, aber er musste während des Entwicklungsprozesses sorgfältig geprüft 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 wird Zeit, die aktivierte Eingabeeigenschaft festzulegen und alle Gestenereignisse zu entfernen. Das Kästchen wird mit state.activethumb.checked = determineChecked() geändert.

determineChecked()

Diese von dragEnd aufgerufene Funktion bestimmt, wo der Daumenstrom innerhalb der Grenzen des Tracks liegt, und gibt „true“ zurück, wenn er gleich oder über der Hälfte der Spur ist:

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 Gedanken

Bei der Drag-Geste war der Code etwas überschuldet, da die ursprüngliche HTML-Struktur ausgewählt wurde. Vor allem wurde die Eingabe in ein Label eingebunden. Da das Label ein übergeordnetes Element ist, erhält es nach der Eingabe Klickinteraktionen. Am Ende des dragEnd-Ereignisses haben Sie padRelease() möglicherweise als seltsam klingende Funktion bemerkt.

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

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

Dadurch wird das Label berücksichtigt, das diesen späteren Klick erhält, da damit die Interaktion eines Nutzers deaktiviert oder überprüft wird.

Wenn ich das noch einmal machen müsste, könnte ich erwägen, während des UX-Upgrades DOM mit JavaScript anzupassen, um ein Element zu erstellen, das die Labelklicks selbst verarbeitet und das integrierte Verhalten nicht beeinträchtigt.

Ich schreibe am wenigsten für diese Art von JavaScript und ich möchte kein bedingtes Ereignis-Bubbling verwalten:

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

Fazit

Diese kurzweilige Switch-Komponente war bisher die meiste Arbeit aller GUI-Herausforderungen! Jetzt weißt du, wie ich es gemacht habe. Wie würdest du es erreichen? 🙂

Diversifizieren wir unsere Ansätze und lernen Sie alle Möglichkeiten kennen, wie wir das Web nutzen können. Erstelle eine Demo und twittere mich über Links, und ich füge sie unten zum Abschnitt über Community-Remixe hinzu.

Community-Remixe

Ressourcen

Suchen Sie den Quellcode von .gui-switch auf GitHub.