Wertblasen für Range Inputs

Avatar of Chris Coyier
Chris Coyier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Range-Inputs in HTML sehen so aus

<input type="range" name="quantity" min="1" max="10">

In Browsern, die sie unterstützen, sehen sie so aus

Das ist ja alles schön und gut. Man könnte es für alles verwenden, wo man eine Zahl von einem Benutzer erfassen möchte, die einen erzwungenen Mindest- und Höchstwert hat.

Aber fällt Ihnen etwas Merkwürdiges auf? Ganz allein kommuniziert dieses Range-Input dem Benutzer nicht, welche Zahl er tatsächlich absenden wird. Wenn Ihre Eingabe etwas wie „Wie fühlen Sie sich? Links für traurig, rechts für glücklich.“ lautet, dann ist das in Ordnung, Sie müssen dem Benutzer wahrscheinlich keine Zahl anzeigen. Aber ich wette, es ist häufiger der Fall, dass Sie die Zahl anzeigen müssen, als sie nicht anzuzeigen.

Um fair zu sein, die Spezifikation besagt

Das Eingabeelement stellt eine Steuerung dar, um den Wert des Elements auf eine Zeichenfolge zu setzen, die eine Zahl darstellt, jedoch mit der Einschränkung, dass der genaue Wert nicht wichtig ist, was es UAs ermöglicht, eine einfachere Schnittstelle bereitzustellen als für den Zahlenstatus.

Aber mal ehrlich, nur weil wir einen coolen Schieberegler wollen, heißt das nicht automatisch, dass wir dem Benutzer vorenthalten sollten, den gesendeten Wert zu kennen. Ich würde nicht unbedingt sagen, dass Browser ihre UI-Steuerung ändern sollten, um diese Zahl anzuzeigen. Ich sage, wir sollten sie selbst bauen!

Dies ist der perfekte Anwendungsfall für das <output>-Tag, das speziell für Werte gedacht ist, die von Formularelementen berechnet werden. Hier ist eine super einfache Implementierung, wie Sie es verwenden könnten

<input type="range" name="foo">
<output for="foo" onforminput="value = foo.valueAsNumber;"></output>

Update! Neue Version mit Vanilla JavaScript, die auch besser funktioniert.

Unser Ziel ist es hier, eine „Blase“ anzuzeigen, die den aktuellen Wert eines Range-Inputs darstellt.

Das Setzen des Werts unserer „Blase“ aus dem Wert des Inputs ist eine Sache des Abgreifens des Range-Werts und des Platzierens im Bubble

range.addEventListener("input", () => {
  bubble.innerHTML = rangel.value;
});

Der *Trick* ist das *Positionieren* der Blase entlang des Range-Inputs, damit sie neben dem „Daumen“ gleitet. Dazu müssen wir berechnen, zu wie viel Prozent die Blase nach links verschoben werden muss. Machen wir also eine Funktion, um das zu tun und die Dinge ein wenig aufzuräumen

range.addEventListener("input", () => {
  setBubble(range, bubble);
});

function setBubble(range, bubble) {
  const val = range.value;
  const min = range.min ? range.min : 0;
  const max = range.max ? range.max : 100;
  const newVal = Number(((val - min) * 100) / (max - min));
  bubble.innerHTML = val;

  // Sorta magic numbers based on size of the native UI thumb
  bubble.style.left = newVal = "%";
}

Hier stellen wir sicher, dass wir die min- und max-Attribute der Range-Inputs berücksichtigen und eine %-Position zwischen 0-100 basierend auf dem aktuellen Wert in diesem Bereich berechnen. Nicht alle Bereiche sind die Standard 0-100 Zahlen. Wenn ein Bereich also auf den Wert 50 in einem Bereich von 0 bis 200 eingestellt wäre, wären das 25 % des Weges. Dies berücksichtigt das.

Aber es hat einen störenden Fehler: Die Blase ist am Anfang zu weit links und am Ende zu weit rechts. Bei Range-Inputs hängt der Daumen nicht am linken Rand, so dass sein Zentrum am Anfang ist, und ebenso am Ende. Wie ein Scrollbalken hören die Ränder des Daumens innerhalb der Spur auf.

Wir können dort einige magische Zahlen verwenden, die sich bei verschiedenen Browsern gut bewähren

// Sorta magic numbers based on size of the native UI thumb
  bubble.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;

Hier ist die finale Demo

Ich wurde dazu inspiriert, mich damit zu beschäftigen, weil Leser Max Globa mit seiner Version schrieb, die ich hier poste

Ein cooler Aspekt von Max' Version ist, dass der Range-Input CSS-gestylt ist, sodass die genaue Größe des Daumens bekannt ist. Es gibt einige Zahlen, die in der JavaScript-Mathematik ziemlich magisch erscheinen, aber zumindest basieren sie auf realen Zahlen, die in CSS auf die Größe des Daumens gesetzt sind.

Andere Versionen

Dave Olsen hat die (ursprüngliche) Idee portiert, um keine Abhängigkeit von jQuery zu haben. Hier ist diese Version


Sean Stopnik


simurai


Vincent Durand


Vergessen Sie nicht, dass Range-Inputs Datalists haben können, die kleine Einkerbungen an ihnen anbringen, was ziemlich cool ist.


Ana Tudor hat eine massive Sammlung, von denen viele den aktuellen Wert durch ihr Design anzeigen.

😬 Alte Version aus der Originalversion dieses Beitrags (jQuery, funktioniert zudem nicht gut)

Ich lasse das hier nur aus historischen Gründen drin.

Holen wir uns unseren Freund jQuery und machen wir uns an CSS. Das Ziel ist unten. Jeder Range-Input, jederzeit, jeder Min/Max/Step – wir setzen eine Blase darüber, die den aktuellen Wert anzeigt.

Formatieren wir zuerst das Output-Element. Wir werden es absolut über dem Input positionieren. Das gibt uns die Möglichkeit, auch den linken Wert anzupassen, sobald wir mit JavaScript herausgefunden haben, was er sein soll. Wir werden es mit Farbverläufen und abgerundeten Ecken aufpeppen und sogar ein kleines Zeigendreieck mit einem Pseudo-Element hinzufügen.

output { 
  position: absolute;
  background-image: linear-gradient(top, #444444, #999999);
  width: 40px; 
  height: 30px; 
  text-align: center; 
  color: white; 
  border-radius: 10px; 
  display: inline-block; 
  font: bold 15px/30px Georgia;
  bottom: 175%;
  left: 0;
  margin-left: -1%;
}
output:after { 
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  border-top: 10px solid #999999;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  top: 100%;
  left: 50%;
  margin-left: -5px;
  margin-top: -1px;
}

Jetzt müssen wir alle Range-Inputs auf Wertänderungen überwachen. Unser Ziel ist es, die linke Position der Blase im Takt mit dem Schieberegler zu verschieben. Das ist nicht ganz einfach, da Schieberegler beliebige Breiten und beliebige Minimal- oder Maximalwerte haben können. Wir werden ein bisschen rechnen müssen. Hier ist das gesamte jQuery JavaScript, kommentiert

// DOM Ready
$(function() {
 var el, newPoint, newPlace, offset;
 
 // Select all range inputs, watch for change
 $("input[type='range']").change(function() {
 
   // Cache this for efficiency
   el = $(this);
   
   // Measure width of range input
   width = el.width();
   
   // Figure out placement percentage between left and right of input
   newPoint = (el.val() - el.attr("min")) / (el.attr("max") - el.attr("min"));
   
   // Janky value to get pointer to line up better
   offset = -1.3;
   
   // Prevent bubble from going beyond left or right (unsupported browsers)
   if (newPoint < 0) { newPlace = 0; }
   else if (newPoint > 1) { newPlace = width; }
   else { newPlace = width * newPoint + offset; offset -= newPoint; }
   
   // Move bubble
   el
     .next("output")
     .css({
       left: newPlace,
       marginLeft: offset + "%"
     })
     .text(el.val());
 })
 // Fake a change to position bubble at page load
 .trigger('change');
});

Das einzig hässliche daran ist der Wert 1.3. Ich habe versucht, die Spitze des Blasen-Dreiecks mit der Mitte des Schiebereglers auszurichten. Das ist nicht einfach, da die Mitte des Schiebereglers nie zu 100 % links oder rechts ist. Dieser Wert ist nicht perfekt, noch perfekt implementiert, aber er ist besser, als ihn nicht zu haben.

Als Bonus erhalten Browser, die den Range-Input nicht unterstützen, trotzdem die Blasen-Funktionalität.

Der obige Code *hängt* davon ab, dass die Range-Inputs einen spezifizierten min- und max-Wert haben. Wenn sie das nicht tun, bricht es irgendwie zusammen. Ich finde es seltsam, einen Range-Input zu verwenden, ohne diese Dinge anzugeben, obwohl sie standardmäßig 0 und 100 zu sein scheinen, wenn man sie nicht angibt. Um dies robust zu machen, würden Sie jedes Attribut abrufen, testen und wenn es nicht richtig aussieht, es beheben. Etwas wie

var minValue, maxValue;
if (!el.attr("min")) { minValue = 0; } else { minValue = el.attr("min"); }

...dann die Variable minValue in der Mathematik verwenden. Und ähnliches für Max. Jedenfalls, hier ist die Live-Demo