Nativer Dual-Range-Input
(muffinman.io)@stanko/dual-range-inputerstellt einen Dual-Range-Slider, indem zwei native HTML-Range-Inputs kombiniert werden, und nutzt dabei die standardmäßigen Interaktionen und die Barrierefreiheit des Browsers unverändert- Die Implementierung basiert im Wesentlichen auf etwa 50 Zeilen JavaScript zur Synchronisierung der Eingabewerte sowie CSS und ist damit einfacher als herkömmliche Ansätze, bei denen Dragging und Barrierefreiheit selbst neu umgesetzt werden
- Die beiden Inputs aktualisieren
min,maxund ihre Breite anhand des Mittelpunkts der ausgewählten Werte; beim Klick auf die Track-Leiste bewegt sich der näher gelegene Thumb zu diesem Wert - Da die Track-Leiste eines Range-Inputs kürzer ist als die tatsächliche Input-Breite, wird die Thumb-Breite in die Berechnung einbezogen und die überschüssige Breite über Padding im Wrapper aufgefangen
- Das Theme lässt sich über CSS-Variablen und Gradienten anpassen, sodass sich etwa Farbe des ausgewählten Bereichs, Thumb-Größe und Track-Farbe überschreiben lassen
Dual-Slider mit nativen Range-Inputs
@stanko/dual-range-inputimplementiert einen Dual-Range-Input, indem zwei HTML-Range-Inputs nebeneinander platziert werden- „Nativ“ bedeutet hier nicht, dass es sich um ein einzelnes Browser-Control handelt, sondern dass durch die Nutzung zweier Range-Inputs die standardmäßigen Interaktionen und Barrierefreiheitsfunktionen des Browsers erhalten bleiben
- Die Implementierung besteht aus etwa 50 Zeilen JavaScript zur Synchronisierung der Inputs und CSS für die visuelle Ausgestaltung
- Ausgangspunkt war der Bedarf an einer UI in einem generativen Zeichenwerkzeug, mit der Minimal- und Maximalwerte von Parametern angepasst werden können
- Es gab zwei Anforderungen
- Es sollten native HTML-Range-Inputs verwendet werden
- Beim Klick auf die Track-Leiste sollte sich von den beiden Thumbs derjenige zum entsprechenden Wert bewegen, der näher an der Klickposition liegt
Wertesynchronisierung und Klickverarbeitung
- Wenn sich einer der beiden Inputs ändert, berechnet die Library den Mittelpunkt zwischen den beiden ausgewählten Werten
- Die Attribute
minundmaxjedes Inputs werden entsprechend dem Mittelpunkt gesetzt - Auch die Breite der beiden Inputs wird anhand des Mittelpunkts aktualisiert
- Die Attribute
- Da die Inputs so skaliert werden, dass sie sich am Mittelpunkt treffen, bewegt sich beim Klick zwischen die beiden Thumbs derjenige Thumb, der der Klickposition näher ist
- Ist die Anzahl der Steps zwischen den beiden Thumbs ungerade, hat der zuletzt verwendete Input Vorrang
CSS-Berechnungen und Styling
- Die Track-Leiste eines Range-Inputs ist kürzer als die gesamte Input-Breite, und der Browser lässt auf beiden Seiten Platz, damit der Thumb nicht nach außen hinausragt
- Wenn die Input-Breiten einfach in 25 % und 75 % aufgeteilt werden, stimmen die tatsächlichen Track-Verhältnisse nicht
- Die Implementierung addiert
--dri-thumb-widthzur Breite jedes Inputs und fügt dem Wrapper Padding hinzu, um die überschüssige Breite aufzunehmen
- Für das Styling werden die CSS-Styling-Funktionen des Browsers für Range-Inputs verwendet
- Track und Thumb werden gestaltet, und der mittlere Bereich, in dem sich die beiden Tracks treffen, wird so behandelt, dass dort kein Border-Radius entsteht
- Themes lassen sich durch Überschreiben von CSS-Variablen erstellen
- Zu den wichtigsten Variablen gehören
--dri-height,--dri-thumb-width,--dri-thumb-color,--dri-track-color,--dri-track-filled-color,--dri-track-border-radiusund weitere
- Zu den wichtigsten Variablen gehören
- Der ausgewählte Bereich wird mit einem CSS-Gradienten gefüllt
- Die Position des Gradienten wird über die Variable
--dri-gradient-positionangegeben - Beim Aktualisieren der Input-Breite im Code wird auch diese Variable mit aktualisiert
- Die Position des Gradienten wird über die Variable
- Diese Implementierung verfolgt den Ansatz, möglichst zuerst native Elemente zu nutzen, statt Dragging und Barrierefreiheit mit einer Custom-Library neu zu implementieren
1 Kommentare
Hacker-News-Kommentare
Ich finde, bei dieser Art von Slider sollte auch der mittlere Bereich immer ziehbar sein. Das heißt: Wenn man die Mitte greift, sollten sich beide Handles gleichzeitig bewegen.
Unity macht das zum Beispiel so: https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...
„Immer“ ist vielleicht etwas zu stark formuliert.
Ich bin der Autor. Freut mich, dass es auf HN gelandet ist.
Ich habe gesehen, dass hier Issues und Bug-Reports eintrudeln; bei mir ist es schon spät, ich schaue mir das morgen an.
Ich habe es gebaut, weil ich es für generative Bildarbeiten brauchte, und ich lieber am Slider ziehe und sehe, wie sich das Bild verändert, als Zahlen einzugeben.
Ich habe versucht, es so minimal wie möglich zu halten.
Ich arbeite derzeit mit SwiftUI, wo es fast schon zu einfach ist, eigene Komponenten zu bauen. Aber wie der Autor erklärt hat, ist das nicht immer die beste Lösung, und native Komponenten sind oft ziemlich vollständig ausgestattet.
Ich habe den Beitrag sogar in dem etwas ungewöhnlichen Browser Firefox auf iOS ausprobiert, und es funktionierte gut. Sehr gut gemacht.
Beim Ausprobieren bin ich direkt auf einen Bug gestoßen. Wenn man den Slider auf 100 - 100 zieht und dann 99 - 99 daraus machen will, geht das nicht.
Von 99 - 100 kommt man ebenfalls nicht dorthin, und von 98 - 99 auch nicht. Die einzige Möglichkeit, zu 99 - 99 zu kommen, besteht darin, die Untergrenze unter 98 zu ziehen.
Wenn man von 98 - 99 auf 97 - 99 heruntergeht, kann man danach zu 99 - 99 gelangen.
Dieser Bug kann überall auftreten; 99 - 99 ließ sich nur am einfachsten reproduzieren, weil es nahe am Ende des Sliders liegt. Allgemein gilt: Wenn man bei einem Zustand X - X ein Ende um einen Schritt bewegt, kann man das andere Ende nicht in die entstandene Lücke hineinziehen. Um die Lücke zu schließen, muss man zuerst das andere Ende weiter wegziehen.
Danke für den Report, ich werde das auf jeden Fall beheben.
Dafür gibt es einen anderen visuellen Bug. Wenn man bei 23-25 startet und auf 24-25 zieht, bewegt sich der rechte Griff bei 25 um ein paar Pixel. Wenn man den linken Griff wieder auf 23 zurücksetzt, geht auch der rechte Griff wieder zurück.
Cool! Ich fand schon immer, dass HTML input eine Implementierung für Range-Eingaben mit zwei Werten braucht, weil das ziemlich sinnvoll ist.
Ein Beispiel wäre: „Ich möchte Produkte filtern, die mindestens X und höchstens Y Widgets haben.“
Allerdings habe ich in Firefox einen Bug gefunden, den ich hervorheben möchte. Jedes Mal, wenn man den Min- oder Max-Handle anklickt, ändert sich der Wert um eine Stufe nach oben oder unten.
Wenn man versucht, exakt in die Mitte des Handles zu klicken, ändert sich der Wert nicht; klickt man aber ein wenig links davon, sinkt der ausgewählte Wert um eine Stufe, und klickt man ein wenig rechts davon, steigt er.
Erwartet hätte ich, dass sich der Wert nur bei einer expliziten Änderungsaktion ändert, also wenn man die Maustaste gedrückt hält und den Mauszeiger tatsächlich bewegt.
Firefox interpretiert solche Klicks so, als hätte man direkt auf die Track geklickt. Diese Library greift da überhaupt nicht ein; das ist einfach die Funktionsweise von Firefox.
Dass es per Tastatur funktioniert, ist großartig. Es funktioniert richtig, sodass man auch rückwärts gehen kann, wenn man Shift-Tab gedrückt hält.
Wirklich gut gemacht, und auch wenn es nur ein kleines Detail ist: Danke, dass diese Interaktion korrekt umgesetzt wurde.
In unserem Designsystem hat ein Praktikant ebenfalls so einen nebeneinander angeordneten Proof of Concept gebaut, aber bei einem Input sprangen ständig einzelne Pixel, und am Ende haben wir es nicht sauber hinbekommen.
Also kam ein zweiter Praktikant mit einer Lösung aus zwei überlagerten input-range-Elementen.
Immer wenn ich eine Range-Eingabe brauchte, habe ich den jQuery-UI-Slider verwendet und musste nur für einen Slider jQuery und jQuery UI zur Seite hinzufügen. Das hier ist wirklich gut.
Edit: Wäre es möglich, eine direkt nutzbare normale JS-Datei vorzubauen statt eines ESM-Moduls?
"export default"am Ende entfernen.https://cdn.jsdelivr.net/npm/@stanko/dual-range-input@0.9.8/...
CSS nicht vergessen.
https://cdn.jsdelivr.net/npm/@stanko/dual-range-input@0.9.8/...
Ich weiß nicht, ob Bug-Reports hier okay sind. Die Handles in den ersten beiden Beispielen fangen Touch-Interaktionen ab und verhindern Scrollen. Bei den drei späteren Beispielen ist das nicht so.
Als Nebeneffekt kann man nicht nach oben ziehen, um das Label unter dem Finger sichtbar zu machen; stattdessen scrollt die Seite, sodass das Label weiter unter dem Finger bleibt.
Mich würde interessieren, welchen Browser und welches Betriebssystem du verwendest. Diese Library nutzt native Inputs und tut daher nichts im Zusammenhang mit dem Blockieren von Scrollen.
Auf meinem iOS-Telefon wird bei allen
input[type=range]-Elementen das Scrollen blockiert, unabhängig davon, ob Styles angewendet sind oder nicht.[1] https://github.com/Stanko/dual-range-input/issues
Der Teil mit „nativ“ ist diskutabel. Es heißt zwar, dass die Library zwei native HTML-Range-Inputs nutzt und dadurch native Interaktionen und Accessibility-Funktionen erhalten bleiben, aber mein Maßstab ist klar:
Wenn es JavaScript braucht, damit es wie beabsichtigt funktioniert, ist es nicht mehr nativ.
Trotzdem verstehe ich, was der Autor meint. Statt einen Haufen per JS bewegter divs selbst zu implementieren, wurden normale Slider kombiniert, was einer nativen Lösung deutlich näherkommt. Wenn cleveres CSS JavaScript nicht ersetzen kann, ist das wahrscheinlich die nächstmögliche Variante.
Unabhängig davon, ob es nativ ist oder nicht: Solche Widgets sind sehr nützlich, wenn man sie einsetzen kann, und in diesem Bereich gab es zu wenige Implementierungen.
Vor dem
popover-Attribut konnte man es ohne JavaScript überhaupt nicht öffnen.Außerdem gibt es native Vanilla-JavaScript-Methoden, die man sinnvoll von Libraries unterscheiden kann, die etwa bei Formularvalidierung die eingebauten Validierungswerkzeuge des Browsers gar nicht nutzen.
Als ich https://spodj.intermer.net/ gebaut habe, habe ich nach so etwas gesucht.
Um die nötige Funktionalität zu bekommen, musste ich React etwas unbeholfen einsetzen, weil ich in Frontend-Entwicklung nicht besonders gut bin.