Der erste Teil dieser zweiteiligen Serie beschrieb, wie wir einen Zwei-Daumen-Slider erhalten können. Nun betrachten wir einen allgemeinen Mehr-Daumen-Fall, aber mit einer anderen und besseren Technik zur Erstellung der Füllungen zwischen den Daumen. Und schließlich tauchen wir in das "Wie" der Gestaltung eines realistischen 3D-aussehenden Sliders und eines flachen ein.
Artikelserie
- Multi-Thumb-Slider: Spezieller Zwei-Thumb-Fall
- Multi-Thumb-Slider: Allgemeiner Fall (Dieser Beitrag)
Ein besserer, flexiblerer Ansatz
Nehmen wir an, wir stapeln auf einem Wrapper-Pseudoelement, das den gleichen Bereich wie die Range-Inputs abdeckt, von links nach rechts verlaufende linear-gradient()-Schichten, die jedem Daumen entsprechen. Jede Gradientenschicht ist von der minimalen Spur bis zur Mittellinie des Daumens voll opak (d.h. Alpha ist 1), danach ist sie voll transparent (d.h. Alpha ist 0).
Beachten Sie, dass die RGB-Werte unerheblich sind, da uns nur die Alpha-Werte interessieren. Ich persönlich verwende die Schlüsselwörter red (für den voll opaken Teil) und transparent im Code, da sie die Aufgabe mit der geringsten Anzahl von Zeichen erfüllen.
Wie berechnen wir die Gradientenstopppositionen, an denen wir von voll opak zu voll transparent wechseln? Nun, diese Positionen liegen immer im Abstand eines Daumenradius vom linken Rand und eines Daumenradius vom rechten Rand, also innerhalb eines Bereichs, der der nutzbaren Breite (der Spurbreite abzüglich des Daumendurchmessers) entspricht.
Das bedeutet, wir addieren zuerst einen Daumenradius. Dann berechnen wir den Fortschritt, indem wir die Differenz zwischen der aktuellen Daumenposition und dem Minimum durch die Differenz (--dif) zwischen dem Maximum und dem Minimum teilen. Dieser Fortschrittswert ist eine Zahl im Intervall [0, 1] – das ist 0, wenn die aktuelle Daumenposition am Minimum des Sliders liegt, und 1, wenn die aktuelle Daumenposition am Maximum des Sliders liegt. Um zu erfahren, wo genau entlang dieses nutzbaren Breitenintervalls wir uns befinden, multiplizieren wir diesen Fortschrittswert mit der nutzbaren Breite.
Die gesuchte Position ist die Summe dieser beiden Längenwerte: des Daumenradius und des Abstands, den wir im nutzbaren Breitenintervall zurückgelegt haben.
Die untenstehende Demo ermöglicht es uns zu sehen, wie alles im 2D-Blick gestapelt aussieht und wie genau die Range-Inputs und die Verläufe auf dem Pseudoelement ihres Elternteils in der 3D-Ansicht geschichtet werden. Sie ist auch interaktiv, sodass wir die Slider-Daumen ziehen und sehen können, wie sich die entsprechende Füllung (die durch eine Verlaufsschicht auf dem Pseudoelement ihres Elternteils erzeugt wird) verändert.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Demo ist am besten in Chrome und Firefox sichtbar.
Nun, das einfache Stapeln dieser Verlaufsschichten liefert uns nicht das gewünschte Ergebnis.
Die Lösung hier ist, diese Verläufe zu mask-Schichten zu machen und sie dann (genauer gesagt, im Falle von CSS-Masken bedeutet dies, ihre Alphas zu XORen) zu XORen.
Wenn Sie eine Auffrischung benötigen, wie XOR funktioniert, hier ist eine: Gegeben zwei Eingaben, ist die Ausgabe dieser Operation 1, wenn die Eingabewerte unterschiedlich sind (einer ist 1 und der andere 0) und 0, wenn die Eingabewerte identisch sind (beide sind 0 oder beide sind 1).
Die Wahrheitstabelle für die XOR-Operation sieht wie folgt aus
| Eingaben | Ausgabe | |
|---|---|---|
| A | B | |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Sie können auch damit in der folgenden interaktiven Demo herumspielen, wo Sie die Eingabewerte umschalten und sehen können, wie sich die Ausgabe ändert
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
In unserem Fall sind die Eingabewerte die Alphas der Verlauf-mask-Schichten entlang der horizontalen Achse. Das XORen mehrerer Schichten bedeutet, dies für die ersten beiden von unten zu tun, dann die dritte von unten mit dem Ergebnis der vorherigen XOR-Operation zu XORen und so weiter. Für unseren speziellen Fall von von links nach rechts verlaufenden Verläufen mit einem Alpha von 1 bis zu einem bestimmten Punkt (bestimmt durch den entsprechenden Daumenwert) und dann 0 sieht es wie unten dargestellt aus (wir beginnen von unten und arbeiten uns nach oben).
Wo beide Schichten von unten einen Alpha-Wert von 1 haben, hat die resultierende Schicht nach dem XORen einen Alpha-Wert von 0. Wo sie unterschiedliche Alpha-Werte haben, hat die resultierende Schicht einen Alpha-Wert von 1. Wo sie beide einen Alpha-Wert von 0 haben, hat die resultierende Schicht einen Alpha-Wert von 0.
Wenn wir weiter nach oben gehen, XORen wir die dritte Schicht mit der resultierenden Schicht, die wir im vorherigen Schritt erhalten haben. Wo beide dieser Schichten denselben Alpha-Wert haben, ist der Alpha-Wert der Schicht, die aus dieser zweiten XOR-Operation resultiert, 0. Wo sie unterschiedliche Alpha-Werte haben, ist der resultierende Alpha-Wert 1.
Ebenso XORen wir dann die vierte Schicht von unten mit der Schicht, die aus der zweiten Stufen-XOR-Operation resultiert.
In Bezug auf CSS bedeutet dies die Verwendung des Werts exclude für den Standardwert mask-composite und den Wert xor für den nicht standardmäßigen Wert -webkit-mask-composite. (Für ein besseres Verständnis von mask-Compositing siehe den Crashkurs.)
Diese Technik liefert uns genau das gewünschte Ergebnis und ermöglicht es uns gleichzeitig, ein einziges Pseudoelement für alle Füllungen zu verwenden. Es ist auch eine Technik, die für eine beliebige Anzahl von Daumen funktioniert. Sehen wir uns an, wie wir das in Code umsetzen können!
Um die Dinge vollständig flexibel zu halten, ändern wir zunächst den Pug-Code so, dass er das Hinzufügen oder Entfernen eines Daumens ermöglicht und alles andere entsprechend aktualisiert, indem wir einfach ein Element aus einem Array von Daumenobjekten hinzufügen oder entfernen, wobei jedes Objekt einen Wert und eine Beschriftung (die nur für Bildschirmleser bestimmt ist) enthält.
- let min = -50, max = 50;
- let thumbs = [
- { val: -15, lbl: 'Value A' },
- { val: 20, lbl: 'Value B' },
- { val: -35, lbl: 'Value C' },
- { val: 45, lbl: 'Value D' }
- ];
- let nv = thumbs.length;
.wrap(role='group' aria-labelledby='multi-lbl'
style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')};
--min: ${min}; --max: ${max}`)
#multi-lbl Multi thumb slider:
- for(let i = 0; i < nv; i++)
label.sr-only(for=`v${i}`) #{thumbs[i].lbl}
input(type='range' id=`v${i}` min=min value=thumbs[i].val max=max)
output(for=`v${i}` style=`--c: var(--v${i})`)
Im speziellen Fall dieser vier Werte sieht der generierte Markup wie folgt aus.
<div class='wrap' role='group' aria-labelledby='multi-lbl'
style='--v0: -15; --v1: 20; --v2: -35; --v3: 45; --min: -50; --max: 50'>
<div id='multi-lbl'>Multi thumb slider:</div>
<label class='sr-only' for='v0'>Value A</label>
<input type='range' id='v0' min='-50' value='-15' max='50'/>
<output for='v0' style='--c: var(--v0)'></output>
<label class='sr-only' for='v1'>Value B</label>
<input type='range' id='v1' min='-50' value='20' max='50'/>
<output for='v1' style='--c: var(--v1)'></output>
<label class='sr-only' for='v2'>Value C</label>
<input type='range' id='v2' min='-50' value='-35' max='50'/>
<output for='v2' style='--c: var(--v2)'></output>
<label class='sr-only' for='v3'>Value D</label>
<input type='range' id='v3' min='-50' value='45' max='50'/>
<output for='v3' style='--c: var(--v3)'></output>
</div>
Wir müssen dem CSS oder JavaScript nichts hinzufügen, damit dies einen funktionsfähigen Slider ergibt, bei dem sich die <output>-Werte beim Ziehen der Slider aktualisieren. Vier <output>-Elemente bei immer noch zwei Spalten für das Wrapper-grid würden jedoch das Layout brechen. Vorerst entfernen wir also die für die <output>-Elemente eingeführte Zeile, positionieren diese Elemente absolut und machen sie nur sichtbar, wenn der entsprechende <input> fokussiert ist. Wir entfernen auch die Überreste der vorherigen Lösung, die beide Pseudoelemente auf dem Wrapper verwendet.
.wrap {
/* same as before */
grid-template-rows: max-content #{$h}; /* only 2 rows now */
&::after {
background: #95a;
// content: ''; // don't display for now
grid-column: 1/ span 2;
grid-row: 3;
}
}
input[type='range'] {
/* same as before */
grid-row: 2; /* last row is second row now */
}
output {
color: transparent;
position: absolute;
right: 0;
&::after {
content: counter(c);
counter-reset: c var(--c);
}
}
Wir werden später mehr tun, um das Ergebnis zu verschönern, aber vorerst haben wir das hier.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Als Nächstes müssen wir diese Daumen-zu-Daumen-Füllungen erhalten. Dies erreichen wir, indem wir die mask-Schichten im Pug generieren und sie in einer --fill-Benutzerdefinierten Eigenschaft auf dem Wrapper platzieren.
//- same as before
- let layers = thumbs.map((c, i) => `linear-gradient(90deg, red calc(var(--r) + (var(--v${i}) - var(--min))/var(--dif)*var(--uw)), transparent 0)`);
.wrap(role='group' aria-labelledby='multi-lbl'
style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')};
--min: ${min}; --max: ${max};
--fill: ${layers.join(', ')}`)
// - same as before
Der generierte HTML-Code für den speziellen Fall von vier Daumen mit diesen Werten ist unten zu sehen. Beachten Sie, dass dies automatisch geändert wird, wenn wir Elemente aus dem ursprünglichen Array hinzufügen oder entfernen.
<div class='wrap' role='group' aria-labelledby='multi-lbl'
style='--v0: -15; --v1: 20; --v2: -35; --v3: 45;
--min: -50; --max: 50;
--fill:
linear-gradient(90deg,
red calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)),
transparent 0)'>
<div id='multi-lbl'>Multi thumb slider:</div>
<label class='sr-only' for='v0'>Value A</label>
<input type='range' id='v0' min='-50' value='-15' max='50'/>
<output for='v0' style='--c: var(--v0)'></output>
<label class='sr-only' for='v1'>Value B</label>
<input type='range' id='v1' min='-50' value='20' max='50'/>
<output for='v1' style='--c: var(--v1)'></output>
<label class='sr-only' for='v2'>Value C</label>
<input type='range' id='v2' min='-50' value='-35' max='50'/>
<output for='v2' style='--c: var(--v2)'></output>
<label class='sr-only' for='v3'>Value D</label>
<input type='range' id='v3' min='-50' value='45' max='50'/>
<output for='v3' style='--c: var(--v3)'></output>
</div>
Beachten Sie, dass dies bedeutet, dass wir die Sass-Variablen, die sich auf Dimensionen beziehen, in CSS-Variablen umwandeln und die Sass-Variablen in den Eigenschaften, die sie verwenden, ersetzen müssen.
.wrap {
/* same as before */
--w: 20em;
--h: 4em;
--d: calc(.5*var(--h));
--r: calc(.5*var(--d));
--uw: calc(var(--w) - var(--d));
background: linear-gradient(0deg, #ccc var(--h), transparent 0);
grid-template: max-content var(--h)/ var(--w);
width: var(--w);
}
Wir setzen unsere mask auf das ::after-Pseudoelement des Wrappers.
.wrap {
/* same as before */
&::after {
content: '';
background: #95a;
grid-column: 1/ span 2;
grid-row: 2;
/* non-standard WebKit version */
-webkit-mask: var(--fill);
-webkit-mask-composite: xor;
/* standard version, supported in Firefox */
mask: var(--fill);
mask-composite: exclude;
}
}
Jetzt haben wir genau das, was wir wollen, und das wirklich Coole an dieser Technik ist, dass wir nur die Daumenobjekte (mit einem Wert und einer Beschriftung für jeden) zum thumbs-Array im Pug-Code hinzufügen oder entfernen müssen – sonst muss sich absolut nichts ändern!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Verschönernde Anpassungen
Was wir bisher haben, ist alles andere als eine schöne Aussicht. Fangen wir also an, das zu beheben!
Option Nr. 1: Ein realistischer Look
Nehmen wir an, wir wollen das Ergebnis erzielen, das unten gezeigt wird.

Ein erster Schritt wäre, die Spur die gleiche height wie der Daumen zu machen und die Spurenden abzurunden. Bis zu diesem Punkt haben wir die Spur mit einem background auf dem .wrap-Element emuliert. Während es technisch möglich ist, eine Spur mit abgerundeten Enden durch überlagerte lineare und radiale Verläufe zu emulieren, ist es wirklich nicht die beste Lösung, besonders wenn der Wrapper noch ein freies Pseudoelement (::before) hat.
.wrap {
/* same as before */
--h: 2em;
--d: var(--h);
&::before, &::after {
border-radius: var(--r);
background: #ccc;
content: '';
grid-column: 1/ span 2;
grid-row: 2;
}
&::after {
background: #95a;
/* non-standard WebKit version */
-webkit-mask: var(--fill);
-webkit-mask-composite: xor;
/* standard version, supported in Firefox */
mask: var(--fill);
mask-composite: exclude;
}
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Verwendung von ::before zur Emulation der Spur eröffnet die Möglichkeit, einen leicht 3D-Look zu erzielen.
<pre rel="SCSS"><code class="language-scss">.wrap {
/* same as before */
&::before, &::after {
/* same as before */
box-shadow: inset 0 2px 3px rgba(#000, .3);
}
&::after {
/* same as before */
background:
linear-gradient(rgba(#fff, .3), rgba(#000, .3))
#95a;
}
}
Ich bin keineswegs ein Designer, daher könnten diese Werte wahrscheinlich für ein besseres Ergebnis angepasst werden, aber wir sehen bereits einen Unterschied.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Das lässt uns mit einem sehr hässlichen Daumen zurück, also beheben wir auch diesen Teil!
Wir nutzen die Technik, mehrere Hintergründe mit unterschiedlichen background-clip (und background-origin) Werten zu überlagern.
@mixin thumb() {
border: solid calc(.5*var(--r)) transparent;
border-radius: 50%; /* make circular */
box-sizing: border-box; /* different between Chrome & Firefox */
/* box-sizing needed now that we have a non-zero border */
background:
linear-gradient(rgba(#000, .15), rgba(#fff, .2)) content-box,
linear-gradient(rgba(#fff, .3), rgba(#000, .3)) border-box,
currentcolor;
pointer-events: auto;
width: var(--d); height: var(--d);
}
Ich habe diese Technik in einem älteren Artikel im Detail beschrieben. Schauen Sie ihn sich unbedingt an, wenn Sie eine Auffrischung benötigen!
Der obige Code würde jedoch fast nichts bewirken, wenn der Wert currentcolor schwarz (#000) ist, was er derzeit ist. Korrigieren wir das und ändern auch den cursor auf den Daumen zu etwas Passenderem.
input[type='range'] {
/* same as before */
color: #eee;
cursor: grab;
&:active { cursor: grabbing; }
}
Das Ergebnis ist sicherlich befriedigender als zuvor.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Etwas anderes, das mich wirklich stört, ist, wie nah der Beschriftungstext am Slider ist. Wir können das beheben, indem wir einen grid-gap auf dem Wrapper einführen.
.wrap {
/* same as before */
grid-gap: .625em;
}
Das schlimmste Problem, das wir immer noch haben, sind diese absolut positionierten Ausgaben in der oberen rechten Ecke. Der beste Weg, das zu beheben, ist die Einführung einer dritten grid-Reihe für sie und ihre Bewegung mit den Daumen.
Die Position der Daumen wird auf ähnliche Weise berechnet wie die scharfen Stopps der Verlaufsschichten, die wir für die Füll-mask verwenden.
Zuerst platzieren wir den linken Rand der Ausgaben entlang der vertikalen Linie, die einen Daumenradius --r vom linken Rand des Sliders entfernt ist. Um die Ausgaben mittig auf dieser vertikalen Linie auszurichten, verschieben wir sie zurück (nach links, in negativer Richtung der x-Achse, daher benötigen wir ein Minuszeichen) um die Hälfte ihrer width (50%, da Prozentwerte in translate()-Funktionen sich auf die Dimensionen des Elements beziehen, auf das die transform angewendet wird).
Um sie mit den Daumen zu bewegen, subtrahieren wir den Minimalwert (--min) vom aktuellen Wert des entsprechenden Daumens (--c), teilen diese Differenz durch die Differenz (--dif) zwischen dem Maximalwert (--max) und dem Minimalwert (--min). Dies ergibt einen Fortschrittswert im Intervall [0, 1]. Wir multiplizieren diesen Wert dann mit der nutzbaren Breite (--uw), die den tatsächlichen Bewegungsbereich beschreibt.
.wrap {
/* same as before */
grid-template-rows: max-content var(--h) max-content;
}
output {
background: currentcolor;
border-radius: 5px;
color: transparent;
grid-column: 1;
grid-row: 3;
margin-left: var(--r);
padding: 0 .375em;
transform: translate(calc((var(--c) - var(--min))/var(--dif)*var(--uw) - 50%));
width: max-content;
&::after {
color: #fff;
content: counter(c);
counter-reset: c var(--c);
}
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Auf den ersten Blick sieht das viel besser aus. Eine genauere Betrachtung zeigt jedoch, dass wir immer noch eine Reihe von Problemen haben.
Das erste ist, dass overflow: hidden einen Teil der <output>-Elemente abschneidet, wenn wir das Ende der Spur erreichen.
Um das zu beheben, müssen wir verstehen, was genau overflow: hidden bewirkt. Es schneidet alles außerhalb des padding-box eines Elements ab, wie die interaktive Demo unten zeigt, wo Sie auf den Code klicken können, um die CSS-Deklaration zu umschalten.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Das bedeutet, eine schnelle Lösung für dieses Problem ist, dem Wrapper .wrap einen ausreichend großen seitlichen padding hinzuzufügen.
padding: 0 2em;
Wir stylen unseren Mehr-Daumen-Slider hier isoliert, aber in Wirklichkeit wird er wahrscheinlich nicht das einzige auf einer Seite sein. Wenn also der Platz begrenzt ist, können wir diesen seitlichen padding durch einen negativen seitlichen margin invertieren.
Wenn die benachbarten Elemente noch die Standardeinstellung position: static haben, sollten die Ausgaben durch die Tatsache, dass wir den Wrapper relativ positioniert haben, über das überlagerte Element gelegt werden. Andernfalls sollte das Anpassen des z-index auf dem .wrap ausreichen.
Das größere Problem ist, dass diese Technik, die wir verwendet haben, zu sehr seltsam aussehenden <output>-Überlappungen führt, wenn wir die Daumen ziehen.
Erhöhen des z-index, wenn das <input> auf das entsprechende <output> fokussiert ist, löst das spezielle Problem der <output>-Überlappungen.
input[type='range'] {
&:focus {
outline: solid 0 transparent;
&, & + output {
color: darkorange;
z-index: 2;
}
}
}
Es ändert jedoch nichts an der zugrunde liegenden Problematik, und das wird offensichtlich, wenn wir den background des body ändern, insbesondere wenn wir ihn zu einem Bild machen, da dies den <output>-Text nicht mehr verbergen lässt.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Das bedeutet, wir müssen überdenken, wie wir die <output>-Elemente im normalen Zustand ausblenden und wie wir sie in einem hervorgehobenen Zustand, wie z. B. :focus, enthüllen. Wir wollen dies auch tun, ohne unser CSS aufzublähen.
Die Lösung ist die Verwendung der Technik, die ich vor etwa einem Jahr im Artikel „DRY Switching with CSS Variables“ beschrieben habe: Verwendung einer hervorgehobenen --hl-Benutzerdefinierten Eigenschaft, deren Wert im normalen Zustand 0 und im hervorgehobenen Zustand (:focus) 1 ist. Wir berechnen auch deren Negation (--nothl).
* {
--hl: 0;
--nothl: calc(1 - var(--hl));
margin: 0;
font: inherit
}
So wie es ist, bewirkt dies noch nichts. Der Trick besteht darin, alle Eigenschaften, die wir zwischen den beiden Zuständen ändern möchten, von --hl und, falls erforderlich, von deren Negation (--nothl) abhängig zu machen.
$hlc: #f90;
@mixin thumb() {
/* same as before */
background-color: $hlc;
}
input[type='range'] {
/* same as before */
filter: grayScale(var(--nothl));
z-index: calc(1 + var(--hl));
&:focus {
outline: solid 0 transparent;
&, & + output { --hl: 1; }
}
}
output {
/* same grid placement */
margin-left: var(--r);
max-width: max-content;
transform: translate(calc((var(--c) - var(--min))/var(--dif)*var(--uw)));
&::after {
/* same as before */
background:
linear-gradient(rgba(#fff, .3), rgba(#000, .3))
$hlc;
border-radius: 5px;
display: block;
padding: 0 .375em;
transform: translate(-50%) scale(var(--hl));
}
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wir sind fast fertig! Wir können auch Übergänge beim Zustandswechsel hinzufügen.
$t: .3s;
input[type='range'] {
/* same as before */
transition: filter $t ease-out;
}
output::after {
/* same as before */
transition: transform $t ease-out;
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Eine letzte Verbesserung wäre, die Füllung mit grayscale() zu versehen, wenn keiner der Daumen fokussiert ist. Dies können wir erreichen, indem wir :focus-within auf unserem Wrapper verwenden.
.wrap {
&::after {
/* same as before */
filter: Grayscale(var(--nothl));
transition: filter $t ease-out;
}
&:focus-within { --hl: 1; }
}
Und das ist es!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Option Nr. 2: Ein flacher Look
Sehen wir uns an, wie wir ein flaches Design erzielen können. Zum Beispiel.

Der erste Schritt ist, die Schatten und Verläufe, die unserer vorherigen Demo einen 3D-Look verleihen, zu entfernen und den background der Spur zu einem wiederholenden Verlauf zu machen.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Größenänderung des Daumens bei :focus kann mit einer skalierenden transform mit einem Faktor gesteuert werden, der von der Hervorhebungs-Umschaltvariable (--hl) abhängt.
@mixin thumb() {
/* same as before */
transform: scale(calc(1 - .5*var(--nothl)));
transition: transform $t ease-out;
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Aber was ist mit den Löchern in der Spur um die Daumen herum?
Die mask-Compositing-Technik ist hier äußerst nützlich. Sie beinhaltet das Überlagern von radialen Verläufen, um an jeder Daumenposition Kreise zu erzeugen, und nachdem wir damit fertig sind, wird das Ergebnis invertiert (d. h. mit einer voll opaken Schicht verarbeitet), um diese Kreise in Löcher zu verwandeln.
Das bedeutet, den Pug-Code etwas zu ändern, damit wir die Liste der radialen Verläufe generieren, die die Kreise für jeden Daumen erzeugen. Im Gegenzug invertieren wir diese im CSS.
//- same as before
- let tpos = thumbs.map((c, i) => `calc(var(--r) + (var(--v${i}) - var(--min))/var(--dif)*var(--uw))`);
- let fill = tpos.map(c => `linear-gradient(90deg, red ${c}, transparent 0)`);
- let hole = tpos.map(c => `radial-gradient(circle at ${c}, red var(--r), transparent 0)`)
.wrap(role='group' aria-labelledby='multi-lbl'
style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')};
--min: ${min}; --max: ${max};
--fill: ${fill.join(', ')};
--hole: ${hole.join(', ')}`)
// -same wrapper content as before
Dies generiert den folgenden Markup.
<div class='wrap' role='group' aria-labelledby='multi-lbl'
style='--v0: -15; --v1: 20; --v2: -35; --v3: 45;
--min: -50; --max: 50;
--fill:
linear-gradient(90deg,
red calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)),
transparent 0),
linear-gradient(90deg,
red calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)),
transparent 0);
--hole:
radial-gradient(circle
at calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)),
red var(--r), transparent 0),
radial-gradient(circle
at calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)),
red var(--r), transparent 0),
radial-gradient(circle
at calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)),
red var(--r), transparent 0),
radial-gradient(circle
at calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)),
red var(--r), transparent 0)'>
<!-- same content as before -->
</div>
Im CSS setzen wir eine mask auf beide Pseudoelemente und geben für jedes eine andere Wertung an. Wir XORen auch die mask-Schichten darauf.
Im Fall von ::before ist die mask die Liste der radial-gradient()-Kreise, die mit einer voll opaken Schicht (die als Inverter dient, um die Kreise in kreisförmige Löcher zu verwandeln) XORed sind. Für ::after ist es die Liste der Füll-linear-gradient()-Schichten.
.wrap {
/* same as before */
&::before, &::after {
content: '';
/* same as before */
--mask: linear-gradient(red, red), var(--hole);
/* non-standard WebKit version */
-webkit-mask: var(--mask);
-webkit-mask-composite: xor;
/* standard version, supported in Firefox */
mask: var(--mask);
mask-composite: exclude;
}
&::after {
background: #95a;
--mask: var(--fill);
}
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Der letzte Schritt besteht darin, die Spur- und Füllhöhen anzupassen und sie vertikal in ihrer Rasterzelle (zusammen mit den Daumen) mittig auszurichten.
.wrap {
/* same as before */
&::before, &::after {
/* same as before */
align-self: center;
height: 6px;
}
}
Nun haben wir unseren gewünschten flachen Mehr-Daumen-Slider!
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wie sieht es mit der Browserunterstützung aus?
Hallo, der erste Teil (gestern veröffentlicht) erwähnt, dass dies im Pre-Chromium-Edge nicht unterstützt wird.
Ich habe es getestet und es funktioniert in aktuellen Versionen von Chrome und Firefox auf Ubuntu und Windows 10 (ich habe angefangen, mit der Technik im August-September herumzuspielen, daher funktionierte sie bereits in den damaligen aktuellen Versionen). Mir wurde auch gesagt, dass die Slider-Demos in Safari funktionieren (auch wenn einige der interaktiven Hilfsdemos nicht). Ich wünschte, ich wüsste etwas über Mobilgeräte, aber ich habe kein Smartphone und keinen Zugang dazu, eines zu benutzen.
Das ist sehr beeindruckende Arbeit! Ich bin demütig angesichts des Könnens, das in diesem Artikel gezeigt wird. Sie haben nicht nur gezeigt, wie es gemacht wird und den Gedankengang bis zum Endergebnis(se) dargestellt, sondern auch die Extrameile zurückgelegt und HTML-basierte Visualisierungen erstellt, wie verschiedene Teile funktionierten.
Ich habe den Artikel absolut genossen und freue mich auf mehr. Hoffentlich werde ich eines Tages auch einen Anwendungsfall für eine so coole Komponente haben :)
Funktioniert weitgehend gut in Fennec/Firefox 68.3 auf Android 7. Das einzige Problem scheinen dynamisch platzierte Elemente zu sein, meistens werden sie einfach nicht angezeigt. Auch die Füllung bleibt grayskaliert, obwohl ich die Daumen fröhlich bewege. Vielleicht ein :focus-Problem auf Touchscreen-Geräten?
Ich wusste, dass ich eines Tages Masken-Compositing lernen müsste...
Diese Serie war enorm beeindruckend!
Ich bin beeindruckt von der Tiefe Ihrer Arbeit.
Ich suchte einfach nach einer Möglichkeit, Markierungen auf Input-Bereichen anzuzeigen, aber das eröffnet eine ganz neue Reihe von Möglichkeiten.
Die Idee, Verläufe und Masken zu verwenden, ist sehr gut. Ich hatte Spans und Tabellen im Sinn. Habe meine Meinung geändert.
Ich habe schnell auf Safari auf dem Mac getestet, es ist immer noch in Ordnung. Ich denke, die Farben werden nicht richtig angezeigt, aber das erfordert mehr Zeit und einen direkten Vergleich. Ich kann mehr tun, wenn Sie ein Beispiel haben, auf das ich mich konzentrieren sollte.