Eine grundlegende Übersicht zum Erstellen einer responsiven und barrierefreien Schalterkomponente.
In diesem Beitrag möchte ich meine Gedanken zu einer Möglichkeit zum Erstellen von Schalterkomponenten teilen. Demo ansehen.
Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Ein Schalter funktioniert ähnlich wie ein Kästchen, stellt aber explizit boolesche Ein- und Aus-Zustände dar.
In dieser Demo wird <input type="checkbox" role="switch">
für den Großteil der Funktionen verwendet. Das hat den Vorteil, dass weder CSS noch JavaScript erforderlich sind, damit die Demo vollständig funktionsfähig und barrierefrei ist. Durch das Laden von CSS wird die Unterstützung von Sprachen, die von rechts nach links geschrieben werden, vertikaler Ausrichtung und Animationen ermöglicht. Wenn Sie JavaScript laden, wird der Schalter beweglich und greifbar.
Benutzerdefinierte Eigenschaften
Die folgenden Variablen stehen für die verschiedenen Teile des Schalters und ihre Optionen. Als oberste Klasse enthält .gui-switch
benutzerdefinierte Eigenschaften, die in allen untergeordneten Komponenten verwendet werden, sowie Einstiegspunkte für die zentrale 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%);
}
}
Thumbnails
Größe, Hintergrundfarbe und Farben für Interaktions-Highlights:
.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 reduzierter Bewegungspräferenz mit dem PostCSS-Plug-in in eine benutzerdefinierte Property eingefügt werden. Dabei wird diese Vorlage für Media-Queries 5 verwendet:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
Markieren & Zeichnen
Ich habe mein <input type="checkbox" role="switch">
-Element in ein <label>
-Element gewickelt, um ihre Beziehung zu bündeln und Unklarheiten bei der Verknüpfung von Kästchen und Label zu vermeiden. Gleichzeitig haben Nutzer die Möglichkeit, mit dem Label zu interagieren, um die Eingabe zu aktivieren oder zu deaktivieren.
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
ist bereits mit einer API und einem Status ausgestattet. Der Browser verwaltet das checked
-Attribut und Eingabeereignisse wie oninput
und onchanged
.
Layouts
Flexbox, Grid und benutzerdefinierte Eigenschaften sind entscheidend für die Beibehaltung der Stile dieser Komponente. Sie zentralisieren Werte, geben ansonsten mehrdeutigen Berechnungen oder Bereichen Namen und ermöglichen eine kleine API für benutzerdefinierte Properties, um Komponenten ganz einfach anzupassen.
.gui-switch
Das Layout der obersten Ebene für den Schalter ist Flexbox. Die Klasse .gui-switch
enthält die privaten und öffentlichen benutzerdefinierten Properties, die die untergeordneten Elemente zum Berechnen ihrer Layouts verwenden.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
Das Flexbox-Layout kann wie jedes andere Flexbox-Layout erweitert und geändert werden.
So platzieren Sie beispielsweise Labels über oder unter einem Schalter oder ändern die flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
Verfolgen
Die Kästchenauswahl wird als Schalter-Track gestaltet, indem die normale appearance: checkbox
entfernt und stattdessen eine eigene Größe angegeben wird:
.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 einzelner Zellenrasterbereich für einen Screenshot erstellt, den du beanspruchen kannst.
Thumbnails
Mit dem Stil appearance: none
wird auch das visuelle Häkchen entfernt, das vom Browser bereitgestellt wird. Diese Komponente verwendet ein Pseudo-Element und die :checked
-Pseudoklasse für die Eingabe, um diesen visuellen Indikator zu ersetzen.
Der Vorschaubereich ist ein untergeordnetes Pseudo-Element, das an input[type="checkbox"]
angehängt ist und sich über dem Track statt darunter stapelt, indem es den Rasterbereich track
beansprucht:
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
Stile
Mit benutzerdefinierten Eigenschaften können Sie eine vielseitige Schalterkomponente erstellen, die sich an Farbschemata, von rechts nach links geschriebene Sprachen und Bewegungseinstellungen anpasst.
Touch-Interaktionsstile
Auf Mobilgeräten fügen Browser Labels und Eingaben Markierungen für Tippaktionen und Textauswahlfunktionen hinzu. Dies wirkte sich negativ auf den Stil und das visuelle Interaktionsfeedback aus, das für diese Umstellung erforderlich war. Mit ein paar 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 sein können. Wenn Sie sie entfernen, müssen Sie 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 Element .gui-switch
zugreift.
.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);
}
Vier benutzerdefinierte Properties bieten eine Vielzahl von Anpassungsoptionen für den Schalter-Track. border: none
wird hinzugefügt, da appearance: none
die Rahmen nicht in allen Browsern aus dem Kästchen entfernt.
Thumbnails
Das Vorschauelement befindet sich bereits rechts track
, benötigt aber Kreisstile:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
Interaktion
Mit benutzerdefinierten Properties können Sie sich auf Interaktionen vorbereiten, bei denen Highlights beim Hovern und Änderungen der Position des Schiebereglers angezeigt werden. Außerdem wird die Einstellung des Nutzers geprüft, bevor die Stilvorlage für die Bewegung oder die hervorgehobenen Elemente beim Hovering geändert wird.
.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 Properties bieten einen einzigen Mechanismus zur Positionierung des Schiebereglers im Titel. Wir haben die Größe des Thumbnails und des Tracks, die wir bei den Berechnungen verwenden, damit der Thumbnail-Cursor richtig versetzt ist und sich innerhalb des Tracks befindet: 0%
und 100%
.
Das input
-Element hat die Positionierungsvariable --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 für Kästchenelemente ändern. Da wir transition: transform
var(--thumb-transition-duration) ease
zuvor bedingt für dieses Element 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, dass diese entkoppelte Orchestrierung gut funktioniert hat. Das Thumb-Element bezieht sich nur auf einen Stil, eine translateX
-Position. Die Eingabe kann alle Komplexität und Berechnungen verwalten.
Branche
Die Unterstützung erfolgte mit einer Modifikatorklasse -vertical
, die dem input
-Element eine Drehung mit CSS-Transformationen hinzufügt.
Durch ein in 3D gedrehtes Element ändert sich 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 Mindestabstand, der erforderlich ist, damit eine vertikale Schaltfläche im Layout wie erwartet dargestellt wird:
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) Linksläufig
Zusammen mit einem CSS-Freund, Elad Schecter, habe ich einen Prototyp für ein ausziehbares Seitenmenü mit CSS-Transformierungen entwickelt, das auch Sprachen von rechts nach links unterstützt, indem eine einzige Variable umgedreht wird. Das liegt daran, dass es in CSS keine logischen Property-Transformationen gibt und es sie möglicherweise auch nie geben wird. Elad hatte die gute Idee, einen benutzerdefinierten Property-Wert zu verwenden, um Prozentsätze umzukehren, damit unsere benutzerdefinierte Logik für logische Transformationen an einem einzigen Ort verwaltet werden kann. Ich habe diese Technik auch bei diesem Schalter verwendet und ich denke, es hat gut funktioniert:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
Eine benutzerdefinierte Eigenschaft namens --isLTR
hat anfangs den Wert 1
, was true
bedeutet, da unser Layout standardmäßig von links nach rechts ausgerichtet ist. Mit dem CSS-Pseudoklass :dir()
wird der Wert dann auf -1
gesetzt, wenn sich die Komponente in einem Layout von rechts nach links befindet.
Verwenden Sie --isLTR
in einer calc()
innerhalb einer Transformation:
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
Die Drehung des vertikalen Schalters berücksichtigt jetzt die gegenüberliegende Seitenposition, die für das Layout von rechts nach links erforderlich ist.
Die translateX
-Transformationen für das Pseudoelement „thumb“ müssen ebenfalls aktualisiert werden, um die Anforderung für die gegenüberliegende Seite zu erfüllen:
.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)
);
}
Mit diesem Ansatz lassen sich zwar nicht alle Anforderungen an ein Konzept wie logische CSS-Transformationen erfüllen, er bietet aber für viele Anwendungsfälle einige DRY-Prinzipien.
Bundesstaaten
Die Verwendung der integrierten input[type="checkbox"]
wäre nicht vollständig, ohne die verschiedenen Status zu berücksichtigen, in denen sie sich befinden kann: :checked
, :disabled
, :indeterminate
und :hover
. :focus
wurde bewusst nicht verändert, sondern nur der Versatz angepasst. Der Fokusring sah in Firefox und Safari gut aus:
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 Status ist der Hintergrund der Eingabe „track“ auf die aktive Farbe und die Position des Schiebereglers 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 optisch anders aus, sondern sollte das Element auch unveränderlich machen.Die Unveränderlichkeit von Interaktionen ist unabhängig vom Browser, aber die visuellen Status benötigen Stile, da appearance: none
verwendet wird.
.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%);
}}
}
}
Dieser Status ist etwas schwierig, da sowohl ein dunkles als auch ein helles Design mit deaktiviertem und aktiviertem Status erforderlich sind. Ich habe für diese Status minimalistische Stile ausgewählt, um die Wartungsbelastung der Stilkombinationen zu verringern.
Unklar
Ein oft vergessener Status ist :indeterminate
, bei dem ein Kästchen weder angeklickt noch abgewählt ist. Das ist ein unterhaltsamer Zustand, er ist einladend und unprätentiös. Eine gute Erinnerung daran, dass boolesche Status Zwischenstatus haben können.
Es ist schwierig, ein Kästchen auf „Unbestimmt“ zu setzen. Das geht nur mit JavaScript:
<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>
Da der Bundesstaat für mich unscheinbar und einladend ist, erschien es mir angemessen, die Position des Schalters 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 hierher bewegen
Hover-Interaktionen sollten eine visuelle Unterstützung für die verbundene Benutzeroberfläche bieten und auch eine Richtung für die interaktive Benutzeroberfläche vorgeben. Wenn diese Option aktiviert ist, wird der Schieberegler mit einem halbtransparenten Ring hervorgehoben, wenn der Mauszeiger auf das Label oder die Eingabe bewegt wird. Diese Hover-Animation weist dann auf das interaktive Vorschauelement hin.
Der Effekt „Hervorheben“ wird mit box-shadow
erstellt. Erhöhen Sie die Größe von --highlight-size
, wenn der Mauszeiger auf eine nicht deaktivierte Eingabe schwebt. Wenn der Nutzer mit Bewegung einverstanden ist, wird die box-shadow
animiert und vergrößert sich. Wenn er mit Bewegung nicht 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
Ich finde, dass ein Schalter, der versucht, eine physische Schnittstelle zu emulieren, etwas unheimlich wirken kann, vor allem diese Art mit einem Kreis in einem Track. iOS hat das mit seinem Schalter richtig gemacht. Sie können ihn hin und her ziehen und es ist sehr zufriedenstellend, diese Option zu haben. Umgekehrt kann ein UI-Element inaktiv erscheinen, wenn ein Ziehen versucht wird und nichts passiert.
Ziehbare „Mag ich“-Bewertungen
Das Pseudo-Element „thumb“ erhält seine Position vom .gui-switch > input
-Element mit dem var(--thumb-position)
-Scope. JavaScript kann einen Inline-Stilwert für die Eingabe bereitstellen, um die Position des Schiebereglers dynamisch zu aktualisieren, sodass es so aussieht, als würde er der Mausbewegung folgen. Wenn der Mauszeiger losgelassen wird, entfernen Sie die Inline-Styles und ermitteln Sie mithilfe der benutzerdefinierten Property --thumb-position
, ob die Mausbewegung eher dem Aus- oder dem Ein-Status entsprach. Dies ist das Rückgrat der Lösung: Mithilfe von Mauszeiger-Ereignissen werden die Mauszeigerpositionen bedingt erfasst, um benutzerdefinierte CSS-Eigenschaften zu ändern.
Da die Komponente bereits zu 100% funktionierte, bevor dieses Script angezeigt wurde, ist es ziemlich aufwendig, das vorhandene Verhalten beizubehalten, z. B. das Klicken auf ein Label, um die Eingabe zu aktivieren oder zu deaktivieren. Unser JavaScript sollte keine Funktionen hinzufügen, die auf Kosten bestehender Funktionen gehen.
touch-action
Ziehen ist eine Geste, eine benutzerdefinierte Geste, was sie zu einem guten Kandidaten für touch-action
-Vorteile macht. Bei diesem Schalter sollte eine horizontale Geste von unserem Script verarbeitet oder eine vertikale Geste für die vertikale Schaltervariante erfasst werden. Mit touch-action
können wir dem Browser mitteilen, welche Touch-Gesten für dieses Element verarbeitet werden sollen, damit ein Script eine Geste ohne Konkurrenz verarbeiten kann.
Im folgenden CSS-Code wird dem Browser mitgeteilt, dass vertikale Touch-Gesten verarbeitet werden sollen, wenn eine Touch-Geste innerhalb dieses Schalters beginnt, und horizontale Touch-Gesten ignoriert werden sollen:
.gui-switch > input {
touch-action: pan-y;
}
Das gewünschte Ergebnis ist eine horizontale Geste, die nicht auch die Seite schwenken oder scrollen lässt. Ein vertikaler Cursor kann innerhalb der Eingabe beginnen und die Seite scrollen, horizontale Cursor werden jedoch benutzerdefiniert verarbeitet.
Dienstprogramme für Pixelwerte
Bei der Einrichtung und beim Ziehen müssen verschiedene berechnete Zahlenwerte aus Elementen abgerufen werden. Die folgenden JavaScript-Funktionen geben berechnete Pixelwerte für eine CSS-Eigenschaft zurück. Im Einrichtungsskript wird es 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 Ziel-Pseudo-Element. 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. Im Funktions-Ereignis-Handler gibt es einige Dinge 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 Script-Held ist state.activethumb
, der kleine Kreis, den dieses Script zusammen mit einem Zeiger positioniert. Das switches
-Objekt ist ein Map()
, bei dem die Schlüssel .gui-switch
sind und die Werte gecachte Grenzen und Größen sind, die das Script effizient halten. Für die Ausrichtung von rechts nach links wird dieselbe benutzerdefinierte CSS-Eigenschaft verwendet wie für --isLTR
. Damit kann die Logik umgekehrt und die Unterstützung für RTL fortgesetzt werden. Auch event.offsetX
ist wertvoll, da es einen Deltawert enthält, der für die Positionierung des Schiebereglers nützlich ist.
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
Mit dieser letzten CSS-Zeile wird die benutzerdefinierte Property festgelegt, die vom „thumb“-Element verwendet wird. Diese Zuweisung würde sich sonst im Laufe der Zeit ändern, aber ein vorheriges Zeigerereignis hat --thumb-transition-duration
vorübergehend auf 0s
gesetzt, wodurch eine sonst verzögerte Interaktion vermieden wird.
dragEnd
Damit der Nutzer den Schalter weit außerhalb des Schalters 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 Möglichkeit haben, frei zu ziehen, und dass die Benutzeroberfläche intelligent genug ist, dies zu berücksichtigen. Die Umstellung war nicht sehr aufwendig, erforderte aber eine sorgfältige Überlegung während des Entwicklungsprozesses.
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. Jetzt können Sie die Eigenschaft „input checked“ festlegen und alle Touch-Ereignisse entfernen. Das Kästchen wird durch state.activethumb.checked = determineChecked()
ersetzt.
determineChecked()
Diese Funktion, die von dragEnd
aufgerufen wird, bestimmt, wo sich der aktuelle Zeiger innerhalb der Grenzen seines Tracks befindet, und gibt „wahr“ zurück, wenn er mindestens 50 % des Tracks zurückgelegt hat:
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
}
Weitere Überlegungen
Aufgrund der gewählten ursprünglichen HTML-Struktur war für die Drag-Geste etwas Code-Schulden erforderlich, vor allem für das Einschließen der Eingabe in ein Label. Da das Label ein übergeordnetes Element ist, erhält es nach der Eingabe Klickinteraktionen. Am Ende des dragEnd
-Ereignisses haben Sie vielleicht die merkwürdig klingende Funktion padRelease()
bemerkt.
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
So wird berücksichtigt, dass das Label diesen Klick später erhält, da die Interaktion eines Nutzers dadurch deaktiviert oder aktiviert wird.
Wenn ich das noch einmal machen würde, würde ich möglicherweise das DOM während der UX-Optimierung mit JavaScript anpassen, um ein Element zu erstellen, das Labelklicks selbst verarbeitet und nicht mit dem integrierten Verhalten in Konflikt steht.
Diese Art von JavaScript ist meine am wenigsten bevorzugte, da ich kein bedingtes Ereignis-Bubbling verwalten möchte:
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
Fazit
Diese winzige Schalterkomponente war am Ende die größte Herausforderung aller GUI-Herausforderungen! Wie würden Sie das machen?
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.
Remixe der Community
- @KonstantinRouda mit einem benutzerdefinierten Element: Demo und Code.
- @jhvanderschee mit einer Schaltfläche: Codepen.
Ressourcen
.gui-switch
Quellcode auf GitHub