Multi-Thumb-Slider: Spezieller Fall mit zwei Daumen

Avatar of Ana Tudor
Ana Tudor am

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

Dies ist ein Konzept, das ich vor einigen Jahren zum ersten Mal entdeckte, als Lea Verou einen Artikel darüber schrieb. Multi-Range-Slider wurden leider seitdem aus der Spezifikation entfernt, aber etwas anderes, das in der Zwischenzeit passiert ist, ist, dass CSS besser geworden ist – und ich auch, also beschloss ich kürzlich, meine eigene Version für 2019 zu erstellen.

In diesem zweiteiligen Artikel gehen wir Schritt für Schritt durch, wie wir zuerst ein Beispiel mit zwei Daumen erstellen und dann die damit verbundenen Probleme identifizieren. Wir werden diese Probleme lösen, zuerst für den Fall mit zwei Daumen, und dann, in Teil zwei, eine bessere Lösung für den Fall mit mehreren Daumen entwickeln.

Beachten Sie, wie sich die Daumen gegenseitig passieren können und wir jede beliebige Reihenfolge haben können, wobei sich die Füllungen dazwischen entsprechend anpassen. Überraschenderweise erfordert das Ganze extrem wenig JavaScript.

Artikelserie

  1. Multi-Thumb-Slider: Spezieller Fall mit zwei Daumen (Dieser Beitrag)
  2. Multi-Thumb-Slider: Allgemeiner Fall

Grundstruktur

Wir benötigen zwei Range-Inputs innerhalb eines Wrappers. Beide haben den gleichen minimalen und maximalen Wert (dies ist sehr wichtig, da sonst nichts richtig funktioniert), den wir als benutzerdefinierte Eigenschaften auf dem Wrapper festlegen (--min und --max). Wir legen auch ihre Werte als benutzerdefinierte Eigenschaften fest (--a und --b).

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  input#a(type='range' min=min value=a max=max)
  input#b(type='range' min=min value=b max=max)

Dies generiert den folgenden Markup

<div class='wrap' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <input id='b' type='range' min='-50' value='20' max='50'/>
</div>

Barrierefreiheitsaspekte

Wir haben zwei Range-Inputs und sie sollten wahrscheinlich jeweils ein <label> haben, aber wir möchten, dass unser Multi-Thumb-Slider ein einzelnes Label hat. Wie lösen wir dieses Problem? Wir können den Wrapper zu einem <fieldset> machen, seine <legend> verwenden, um den gesamten Multi-Thumb-Slider zu beschreiben, und für jeden unserer Range-Inputs ein <label> haben, das nur für Screenreader sichtbar ist. (Dank Zoltan für diesen großartigen Vorschlag.)

Aber was ist, wenn wir ein flex- oder grid-Layout auf unserem Wrapper haben wollen? Das wollen wir wahrscheinlich, da die einzige andere Option die absolute Positionierung ist und diese ihre eigenen Probleme mit sich bringt. Dann stoßen wir auf ein Chromium- Problem, bei dem <fieldset> kein flex- oder grid-Container sein kann.

Um dies zu umgehen, verwenden wir das folgende ARIA-Äquivalent (das ich diesem Beitrag von Steve Faulkner entnommen habe)

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(role='group' aria-labelledby='multi-lbl' style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  #multi-lbl Multi thumb slider:
  label.sr-only(for='a') Value A:
  input#a(type='range' min=min value=a max=max)
  label.sr-only(for='b') Value B:
  input#b(type='range' min=min value=b max=max)

Der generierte Markup ist nun

<div class='wrap' role='group' aria-labelledby='multi-lbl' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='a'>Value A:</label>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <label class='sr-only' for='b'>Value B:</label>
  <input id='b' type='range' min='-50' value='20' max='50'/>
</div>

Wenn wir einem Element ein aria-label- oder ein aria-labelledby-Attribut geben, müssen wir ihm auch eine role geben.

Grundlegende Formatierung

Wir machen den Wrapper zu einem mittig ausgerichteten grid mit zwei Zeilen und einer Spalte. Die untere Grid-Zelle erhält die gewünschten Abmessungen für den Slider, während die obere Zelle die gleiche Breite wie der Slider erhält, sich aber in der Höhe an den Inhalt des Gruppenlabels anpassen kann.

$w: 20em;
$h: 1em;

.wrap {
  display: grid;
  grid-template-rows: max-content $h;
  margin: 1em auto;
  width: $w;
}

Um die <label>-Elemente visuell zu verbergen, positionieren wir sie absolut und beschneiden sie auf nichts.

.wrap {
  // same as before
  overflow: hidden; // in case <label> elements overflow
  position: relative;
}

.sr-only {
  position: absolute;
  clip-path: inset(50%);
}

Manche Leute schreien vielleicht über die Unterstützung von clip-path, wie z. B. dass die Verwendung es vor Chromium Edge und Internet Explorer ausschneidet, aber in diesem speziellen Fall spielt das keine Rolle! Wir werden gleich zum Grund dafür kommen.

Wir platzieren die Slider, einen über dem anderen, in der unteren Grid-Zelle.

input[type='range'] {
  grid-column: 1;
  grid-row: 2;
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wir können jedoch bereits ein Problem feststellen: Nicht nur, dass die obere Slider-Schiene über dem Daumen des unteren erscheint, sondern der obere Slider macht es uns auch unmöglich, den unteren mit einer Maus oder Berührung anzuklicken und damit zu interagieren.

Um dies zu beheben, entfernen wir jegliche Schienenhintergründe und -rahmen und heben den Schienenbereich hervor, indem wir stattdessen einen background auf dem Wrapper setzen. Wir setzen auch pointer-events: none auf die eigentlichen <input>-Elemente und setzen sie dann für ihre Daumen wieder auf auto.

@mixin track() {
  background: none; /* get rid of Firefox track background */
  height: 100%;
  width: 100%;
}

@mixin thumb() {
  background: currentcolor;
  border: none; /* get rid of Firefox thumb border */
  border-radius: 0; /* get rid of Firefox corner rounding */
  pointer-events: auto; /* catch clicks */
  width: $h; height: $h;
}

.wrap {
  /* same as before */
  background: /* emulate track with wrapper background */ 
    linear-gradient(0deg, #ccc $h, transparent 0);
}

input[type='range'] {
  &::-webkit-slider-runnable-track, 
  &::-webkit-slider-thumb, & { -webkit-appearance: none; }
  
  /* same as before */
  background: none; /* get rid of white Chrome background */
  color: #000;
  font: inherit; /* fix too small font-size in both Chrome & Firefox */
  margin: 0;
  pointer-events: none; /* let clicks pass through */
  
  &::-webkit-slider-runnable-track { @include track; }
  &::-moz-range-track { @include track; }
  
  &::-webkit-slider-thumb { @include thumb; }
  &::-moz-range-thumb { @include thumb; }
}

Beachten Sie, dass wir einige weitere Stile auf dem input selbst sowie auf der Schiene und dem Daumen gesetzt haben, um das Aussehen über Browser hinweg konsistent zu gestalten, die das Durchlassen von Klicks durch die eigentlichen input-Elemente und ihre Schienen zulassen, während sie auf den Daumen erlaubt sind. Dies schließt vor-Chromium Edge und IE aus, weshalb wir das -ms--Präfix nicht aufgenommen haben – es hat keinen Sinn, etwas zu stylen, das in diesen Browsern sowieso nicht funktional wäre. Deshalb können wir auch clip-path verwenden, um die <label>-Elemente zu verbergen.

Wenn Sie mehr über Standard-Browser-Stile erfahren möchten, um zu verstehen, was hier überschrieben werden muss, können Sie sich diesen Artikel ansehen, in dem ich mich eingehend mit Range-Inputs beschäftige (und auch die Gründe für die Verwendung von Mixins hier erläutere).

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Okay, wir haben jetzt etwas, das funktional aussieht. Aber um es wirklich funktionsfähig zu machen, müssen wir zur JavaScript übergehen!

Funktionalität

Die JavaScript ist ziemlich einfach. Wir müssen die benutzerdefinierten Eigenschaften aktualisieren, die wir auf dem Wrapper gesetzt haben. (Für einen tatsächlichen Anwendungsfall würden sie weiter oben im DOM gesetzt werden, damit sie auch von den Elementen geerbt werden, deren Stile davon abhängen.)

addEventListener('input', e => {
  let _t = e.target;
  _t.parentNode.style.setProperty(`--${_t.id}`, +_t.value)
}, false);

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Es ist jedoch nicht wirklich offensichtlich, dass dies etwas bewirkt, es sei denn, wir öffnen DevTools, um zu sehen, dass sich die Werte dieser beiden benutzerdefinierten Eigenschaften im style-Attribut des Wrapper-.wrap tatsächlich ändern. Also, lassen Sie uns etwas dagegen tun!

Werte anzeigen

Um deutlich zu machen, dass das Ziehen der Daumen tatsächlich etwas ändert, können wir die aktuellen Werte anzeigen. Dazu verwenden wir ein output-Element für jeden input.

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(role='group' aria-labelledby='multi-lbl' style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  #multi-lbl Multi thumb slider:
  label.sr-only(for='a') Value A:
  input#a(type='range' min=min value=a max=max)
  output(for='a' style='--c: var(--a)')
  label.sr-only(for='b') Value B:
  input#b(type='range' min=min value=b max=max)
  output(for='b' style='--c: var(--b)')

Der resultierende HTML sieht wie folgt aus

<div class='wrap' role='group' aria-labelledby='multi-lbl' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='a'>Value A:</label>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <output for='a' style='--c: var(--a)'></output>
  <label class='sr-only' for='b'>Value B:</label>
  <input id='b' type='range' min='-50' value='20' max='50'/>
  <output for='b' style='--c: var(--b)'></output>
</div>

Wir zeigen die Werte in einem ::after-Pseudoelement mit einem kleinen counter-Trick an.

output {
  &::after {
    counter-reset: c var(--c);
    content: counter(c);
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Es ist jetzt offensichtlich, dass sich diese Werte ändern, wenn wir die Slider ziehen, aber das Ergebnis ist hässlich und hat die Ausrichtung des Wrapper-backgrounds gestört. Lassen Sie uns also ein paar Tweaks hinzufügen! Wir könnten die <output>-Elemente absolut positionieren, aber vorerst quetschen wir sie einfach in eine Reihe zwischen das Gruppenlabel und die Slider.

.wrap {
  // same as before
  grid-template: repeat(2, max-content) #{$h}/ 1fr 1fr;
}

[id='multi-lbl'] { grid-column: 1/ span 2 }

input[type='range'] {
  // same as before
  grid-column: 1/ span 2;
  grid-row: 3;
}

output {
  grid-row: 2;
  
  &:last-child { text-align: right; }
  
  &::after {
    content: '--' attr(for) ': ' counter(c) ';'
    counter-reset: c var(--c);
  }
}

Viel besser!

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das Setzen separater :focus-Stile ergibt sogar etwas, das nicht schlecht aussieht, und ermöglicht uns zu sehen, welchen Wert wir gerade bearbeiten.

input[type='range'] {
  /* same as before */
  z-index: 1;

  &:focus {
    z-index: 2;
    outline: dotted 1px currentcolor;
    
    &, & + output { color: darkorange }
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Alles, was wir jetzt noch brauchen, ist die Erstellung der Füllung zwischen den Daumen.

Der knifflige Teil

Wir können die Füllung mit einem ::after-Pseudoelement auf dem Wrapper nachbilden, das wir auf der unteren Grid-Reihe platzieren, wo wir auch die Range-Inputs platziert haben. Dieses Pseudoelement kommt, wie der Name schon sagt, nach den Inputs, wird aber trotzdem darunter angezeigt, da wir ihnen positive z-index-Werte zugewiesen haben. Beachten Sie, dass das Setzen des z-index auf den Inputs funktioniert (ohne explizit deren position auf etwas anderes als static zu setzen), da sie grid-Kinder sind.

Die width dieses Pseudoelements sollte proportional zur Differenz zwischen dem höheren input-Wert und dem niedrigeren input-Wert sein. Das große Problem hier ist, dass sie sich gegenseitig passieren und wir keine Möglichkeit haben zu wissen, welcher den höheren Wert hat.

Erster Ansatz

Meine erste Idee zur Lösung dieses Problems war die Kombination von width und min-width. Um besser zu verstehen, wie dies funktioniert, betrachten wir, dass wir zwei Prozentwerte haben, --a und --b, und wir möchten, dass die width eines Elements der absolute Wert der Differenz zwischen ihnen ist.

Entweder der eine oder der andere Wert kann der größere sein, also wählen wir ein Beispiel, bei dem --b größer ist, und ein Beispiel, bei dem --a größer ist.

<div style='--a: 30%; --b: 50%'><!-- first example, --b is bigger --></div>
<div style='--a: 60%; --b: 10%'><!-- second example, --a is bigger --></div>

Wir setzen width auf den zweiten Wert (--b) minus den ersten (--a) und min-width auf den ersten Wert (--a) minus den zweiten (--b).

div {
  background: #f90;
  height: 4em;
  min-width: calc(var(--a) - var(--b));
  width: calc(var(--b) - var(--a));
}

Wenn der zweite Wert (--b) größer ist, ist die width positiv (was sie gültig macht) und die min-width negativ (was sie ungültig macht). Das bedeutet, dass der berechnete Wert der über die width-Eigenschaft gesetzte ist. Dies ist im ersten Beispiel der Fall, bei dem --b 70% und --a 50% beträgt. Das bedeutet, dass die width zu 70% - 50% = 20% berechnet wird, während die min-width zu 50% - 70% = -20% berechnet wird.

Wenn der erste Wert größer ist, ist die width negativ (was sie ungültig macht) und die min-width positiv (was sie gültig macht), was bedeutet, dass der berechnete Wert der über die min-width-Eigenschaft gesetzte ist. Dies ist im zweiten Beispiel der Fall, bei dem --a 80% und --b 30% beträgt, was bedeutet, dass die width zu 30% - 80% = -50% berechnet wird, während die min-width zu 80% - 30% = 50% berechnet wird.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wenn wir diese Lösung für unseren Zwei-Daumen-Slider anwenden, erhalten wir

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
  
  &::after {
    content: '';
    background: #95a;
    grid-column: 1/ span 2;
    grid-row: 3;
    min-width: calc((var(--a) - var(--b))/var(--dif)*100%);
    width: calc((var(--b) - var(--a))/var(--dif)*100%);
  }
}

Um die Werte für width und min-width als Prozentsätze darzustellen, müssen wir die Differenz zwischen unseren beiden Werten durch die Differenz (--dif) zwischen dem Maximum und dem Minimum der Range-Inputs teilen und das Ergebnis dann mit 100% multiplizieren.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Bisher gut... und dann?

Die ::after hat immer die richtige berechnete width, aber wir müssen sie auch vom Schienenminimum um den kleineren Wert versetzen, und wir können nicht denselben Trick für ihre margin-left-Eigenschaft verwenden.

Mein erster Impuls war hier, left zu verwenden, aber tatsächliche Verschiebungen funktionieren nicht von allein. Wir müssten auch explizit position: relative auf unser ::after-Pseudoelement setzen, um es zu ermöglichen. Ich fand das irgendwie meh, also habe ich stattdessen margin-left gewählt.

Die Frage ist, welchen Ansatz wir für diese zweite Eigenschaft verfolgen können. Der, den wir für die width verwendet haben, funktioniert nicht, da es so etwas wie min-margin-left nicht gibt.

Eine min()-Funktion ist jetzt in der CSS-Spezifikation enthalten, aber zu der Zeit, als ich diese Multi-Thumb-Slider programmierte, war sie nur von Safari implementiert (sie ist inzwischen auch in Chrome angekommen). Nur Safari-Unterstützung reichte mir nicht aus, da ich kein Apple-Gerät besitze und auch niemanden persönlich kenne, der eines hat... also konnte ich nicht mit dieser Funktion spielen! Und die Unfähigkeit, eine Lösung zu finden, die ich tatsächlich testen konnte, bedeutete eine Änderung des Ansatzes.

Zweiter Ansatz

Dies beinhaltet die Verwendung beider Pseudoelemente unseres Wrappers (.wrap): die margin-left und width eines Pseudoelements werden so gesetzt, als wäre der zweite Wert größer, und die des anderen, als wäre der erste Wert größer.

Mit dieser Technik ist, wenn der zweite Wert größer ist, die width, die wir auf ::before setzen, positiv und die, die wir auf ::after setzen, negativ (was bedeutet, dass sie ungültig ist und der Standardwert von 0 angewendet wird, wodurch dieses Pseudoelement ausgeblendet wird). Wenn hingegen der erste Wert größer ist, ist die width, die wir auf ::before setzen, negativ (sodass es dieses Pseudoelement ist, das eine berechnete width von 0 hat und in dieser Situation nicht angezeigt wird) und die, die wir auf ::after setzen, positiv.

Ähnlich verwenden wir den ersten Wert (--a), um die margin-left-Eigenschaft auf ::before zu setzen, da wir für dieses Pseudoelement annehmen, dass der zweite Wert --b größer ist. Das bedeutet, --a ist der Wert des linken Endes und --b der Wert des rechten Endes.

Für ::after verwenden wir den zweiten Wert (--b), um die margin-left-Eigenschaft zu setzen, da wir für dieses Pseudoelement annehmen, dass der erste Wert --a größer ist. Das bedeutet, --b ist der Wert des linken Endes und --a der Wert des rechten Endes.

Schauen wir uns an, wie wir dies für die gleichen beiden Beispiele, die wir zuvor hatten, in Code umsetzen, wobei eines --b größer hat und ein anderes, bei dem --a größer ist.

<div style='--a: 30%; --b: 50%'></div>
<div style='--a: 60%; --b: 10%'></div>
div {
  &::before, &::after {
    content: '';
    height: 5em;
  }
  
  &::before {
    margin-left: var(--a);
    width: calc(var(--b) - var(--a));
  }

  &::after {
    margin-left: var(--b);
    width: calc(var(--a) - var(--b));
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wenn wir diese Technik für unseren Zwei-Daumen-Slider anwenden, erhalten wir

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
  
  &::before, &::after {
    grid-column: 1/ span 2;
    grid-row: 3;
    height: 100%;
    background: #95a;
    content: ''
  }
  
  &::before {
    margin-left: calc((var(--a) - var(--min))/var(--dif)*100%);
    width: calc((var(--b) - var(--a))/var(--dif)*100%)
  }
  
  &::after {
    margin-left: calc((var(--b) - var(--min))/var(--dif)*100%);
    width: calc((var(--a) - var(--b))/var(--dif)*100%)
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wir haben jetzt einen schönen funktionalen Slider mit zwei Daumen. Aber diese Lösung ist alles andere als perfekt.

Probleme

Das erste Problem ist, dass wir die Werte für margin-left und width nicht ganz richtig hinbekommen haben. In dieser Demo ist es einfach nicht bemerkbar aufgrund der Daumen-Styling (wie Form, Abmessungen relativ zur Schiene und volle Opazität).

Aber sagen wir, unser Daumen ist rund und vielleicht sogar kleiner als die height der Schiene.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wir können jetzt sehen, was das Problem ist: Die Endlinien der Füllung stimmen nicht mit den vertikalen Mittellinien der Daumen überein.

Dies liegt an der Art und Weise, wie die Bewegung der Daumen von einem Ende zum anderen funktioniert. In Chrome bewegt sich der border-box des Daumens innerhalb der Grenzen des content-box der Schiene, während er sich in Firefox innerhalb der Grenzen des content-box des Sliders bewegt. Dies ist in den untenstehenden Aufnahmen zu sehen, wo der padding transparent ist, während der content-box und der border halbtransparent sind. Wir haben Orange für den eigentlichen Slider, Rot für die Schiene und Lila für den Daumen verwendet.

Animated gif. Chrome only moves the thumb within the left and right limits of the track's content-box.
Aufnahme der Daumenbewegung in Chrome von einem Ende des Sliders zum anderen.

Beachten Sie, dass die width der Schiene in Chrome immer durch die des übergeordneten Sliders bestimmt wird – jeder width-Wert, den wir der Schiene selbst zuweisen, wird ignoriert. Dies ist in Firefox nicht der Fall, wo die Schiene auch breiter oder schmaler als ihr übergeordnetes <input> sein kann. Wie wir unten sehen können, wird dadurch noch deutlicher, dass die Bewegungsreichweite des Daumens in diesem Browser ausschließlich von der width des Sliders abhängt.

Animated gif. Firefox moves the thumb within the left and right limits of the actual range input's content-box.
Aufnahme der Daumenbewegung in Firefox von einem Ende des Sliders zum anderen. Die drei Fälle sind von oben nach unten dargestellt. Der border-box der Schiene passt perfekt zum content-box des Sliders horizontal. Er ist länger und kürzer).

In unserem speziellen Fall (und, fairerweise, in vielen anderen Fällen) können wir darauf verzichten, margin, border oder padding auf der Schiene zu haben. Das würde bedeuten, dass ihre content-box mit der des eigentlichen Range-input übereinstimmt, sodass es keine Inkonsistenzen zwischen den Browsern gibt.

Aber wir müssen bedenken, dass die vertikalen Mittellinien der Daumen (die mit den Füllungsendlinien übereinstimmen müssen) sich zwischen der halben Daumenbreite (oder einem Daumenradius, wenn wir einen runden Daumen haben) vom Anfang der Schiene und der halben Daumenbreite vom Ende der Schiene bewegen. Das ist ein Intervall, das der Schienenbreite abzüglich der Daumenbreite (oder dem Daumendurchmesser im Fall eines runden Daumens) entspricht.

Dies ist in der interaktiven Demo unten zu sehen, wo der Daumen gezogen werden kann, um das Intervall besser zu sehen, innerhalb dessen sich seine vertikale Mittellinie (die mit der Endlinie der Füllung übereinstimmen muss) bewegt.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Die Demo ist am besten in Chrome und Firefox sichtbar.

Die Werte für die width und margin-left der Füllung sind nicht relativ zu 100% (oder der Schienenbreite), sondern zur Schienenbreite minus der Daumenbreite (was im speziellen Fall eines runden Daumens auch der Durchmesser ist). Außerdem beginnen die margin-left-Werte nicht bei 0, sondern bei der halben Daumenbreite (was in unserem speziellen Fall einem Daumenradius entspricht).

$d: .5*$h; // thumb diameter
$r: .5*$d; // thumb radius
$uw: $w - $d; // useful width

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
	
  &::before {
    margin-left: calc(#{$r} + (var(--a) - var(--min))/var(--dif)*#{$uw});
    width: calc((var(--b) - var(--a))/var(--dif)*#{$uw});
  }
  
  &::after {
    margin-left: calc(#{$r} + (var(--b) - var(--min))/var(--dif)*#{$uw});
    width: calc((var(--a) - var(--b))/var(--dif)*#{$uw});
  }
}

Jetzt beginnt und endet die Füllung genau dort, wo sie soll, entlang der Mittellinien der beiden Daumen.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Dieses eine Problem wurde behoben, aber wir haben immer noch ein viel größeres. Nehmen wir an, wir wollen mehr Daumen, sagen wir vier.

Animated gif. Shows a slider with four thumbs which can pass each other and be in any order, while the fills are always between the two thumbs with the two smallest values and between the two thumbs with the two biggest values, regardless of their order in the DOM.
Ein Beispiel mit vier Daumen.

Wir haben jetzt vier Daumen, die sich alle gegenseitig passieren können und in jeder beliebigen Reihenfolge sein können, die wir nicht kennen. Außerdem haben wir nur zwei Pseudoelemente, also können wir nicht dieselben Techniken anwenden. Können wir trotzdem eine CSS-Lösung finden?

Nun, die Antwort ist ja! Aber das bedeutet, diese Lösung zu verwerfen und sich für etwas anderes und viel Raffinierteres zu entscheiden – in Teil zwei dieses Artikels!

Artikelserie

  1. Multi-Thumb-Slider: Spezieller Fall mit zwei Daumen (Dieser Beitrag)
  2. Multi-Thumb-Slider: Allgemeiner Fall (Kommt morgen!)