Früher dachte ich, die Implementierung von Wischgesten müsste sehr schwierig sein, aber kürzlich fand ich mich in einer Situation wieder, in der ich es tun musste, und entdeckte, dass die Realität lange nicht so düster ist, wie ich es mir vorgestellt hatte.
Dieser Artikel wird Sie Schritt für Schritt durch die Implementierung mit der geringstmöglichen Codezeile führen. Lassen Sie uns also direkt einsteigen!
Die HTML-Struktur
Wir beginnen mit einem .container, der eine Reihe von Bildern enthält.
<div class='container'>
<img src='img1.jpg' alt='image description'/>
...
</div>
Grundlegende Stile
Wir verwenden display: flex, um sicherzustellen, dass die Bilder nebeneinander und ohne Abstände angezeigt werden. align-items: center richtet sie vertikal in der Mitte aus. Wir sorgen dafür, dass sowohl die Bilder als auch der Container die width des Elternelements des Containers (in unserem Fall body) einnehmen.
.container {
display: flex;
align-items: center;
width: 100%;
img {
min-width: 100%; /* needed so Firefox doesn't make img shrink to fit */
width: 100%; /* can't take this out either as it breaks Chrome */
}
}
Die Tatsache, dass sowohl der .container als auch seine Kindbilder die gleiche width haben, bewirkt, dass diese Bilder auf der rechten Seite überlappen (wie durch den roten Umriss hervorgehoben), wodurch eine horizontale Scrollleiste entsteht, aber genau das wollen wir.

Da nicht alle Bilder die gleichen Abmessungen und Seitenverhältnisse haben, gibt es über und unter einigen von ihnen etwas weißen Platz. Diesen werden wir abschneiden, indem wir dem .container eine explizite height geben, die für das durchschnittliche Seitenverhältnis dieser Bilder gut funktionieren sollte, und overflow-y auf hidden setzen.
.container {
/* same as before */
overflow-y: hidden;
height: 50vw;
max-height: 100vh;
}
Das Ergebnis ist unten zu sehen, wobei alle Bilder auf die gleiche height zugeschnitten sind und keine leeren Räume mehr vorhanden sind.

overflow-y auf dem .container zugeschnitten wurden (siehe Live-Demo).Okay, aber jetzt haben wir eine horizontale Scrollleiste am .container selbst. Nun, das ist eigentlich gut für den Fall ohne JavaScript.
Andernfalls erstellen wir eine CSS-Variable --n für die Anzahl der Bilder und verwenden diese, um den .container breit genug zu machen, um alle seine Bildkinder aufzunehmen, die immer noch die gleiche Breite wie sein Elternelement (in diesem Fall das body) haben.
.container {
--n: 1;
width: 100%;
width: calc(var(--n)*100%);
img {
min-width: 100%;
width: 100%;
width: calc(100%/var(--n));
}
}
Beachten Sie, dass wir die vorherigen width-Deklarationen als Fallbacks beibehalten. Die calc()-Werte ändern nichts, bis wir --n über JavaScript festlegen, nachdem wir unseren .container und die Anzahl der darin enthaltenen Kindbilder ermittelt haben.
const _C = document.querySelector('.container'),
N = _C.children.length;
_C.style.setProperty('--n', N)
Jetzt hat sich unser .container erweitert, um alle Bilder darin unterzubringen.
Bilder wechseln
Als Nächstes entfernen wir die horizontale Scrollleiste, indem wir overflow-x: hidden auf dem Elternelement unseres Containers (in unserem Fall body) setzen, und erstellen eine weitere CSS-Variable, die den Index des aktuell ausgewählten Bildes (--i) enthält. Wir verwenden dies, um den .container relativ zum Viewport durch eine Translation korrekt zu positionieren (denken Sie daran, dass %-Werte innerhalb von translate()-Funktionen sich auf die Abmessungen des Elements beziehen, auf das wir diese transform angewendet haben).
body { overflow-x: hidden }
.container {
/* same styles as before */
transform: translate(calc(var(--i, 0)/var(--n)*-100%));
}
Durch Ändern von --i auf einen anderen ganzzahligen Wert, der größer oder gleich Null, aber kleiner als --n ist, wird ein anderes Bild sichtbar, wie in der interaktiven Demo unten gezeigt (wobei der Wert von --i durch einen Bereichseingabefeld gesteuert wird).
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Okay, aber wir wollen keinen Schieberegler dafür verwenden.
Die Grundidee ist, dass wir die Bewegungsrichtung zwischen dem "touchstart"- (oder "mousedown"-) Ereignis und dem "touchend"- (oder "mouseup"-) Ereignis erkennen und dann --i entsprechend aktualisieren, um den Container so zu verschieben, dass das nächste Bild (falls vorhanden) in der gewünschten Richtung in den Viewport bewegt wird.
function lock(e) {};
function move(e) {};
_C.addEventListener('mousedown', lock, false);
_C.addEventListener('touchstart', lock, false);
_C.addEventListener('mouseup', move, false);
_C.addEventListener('touchend', move, false);
Beachten Sie, dass dies für die Maus nur funktioniert, wenn wir pointer-events: none für die Bilder setzen.
.container {
/* same styles as before */
img {
/* same styles as before */
pointer-events: none;
}
}
Außerdem muss Edge die Touch-Ereignisse ab about:flags aktiviert haben, da diese Option standardmäßig deaktiviert ist.

Bevor wir die Funktionen lock() und move() füllen, vereinheitlichen wir die Touch- und Klickfälle.
function unify(e) { return e.changedTouches ? e.changedTouches[0] : e };
Das Sperren bei "touchstart" (oder "mousedown") bedeutet, dass die x-Koordinate abgerufen und in einer anfänglichen Koordinatenvariable x0 gespeichert wird.
let x0 = null;
function lock(e) { x0 = unify(e).clientX };
Um zu sehen, wie wir unseren .container bewegen (oder ob wir das überhaupt tun, weil wir uns nicht weiter bewegen wollen, wenn wir das Ende erreicht haben), prüfen wir, ob wir die lock()-Aktion durchgeführt haben, und wenn ja, lesen wir die aktuelle x-Koordinate, berechnen die Differenz zwischen ihr und x0 und entscheiden basierend auf ihrem Vorzeichen und dem aktuellen Index, was zu tun ist.
let i = 0;
function move(e) {
if(x0 || x0 === 0) {
let dx = unify(e).clientX - x0, s = Math.sign(dx);
if((i > 0 || s < 0) && (i < N - 1 || s > 0))
_C.style.setProperty('--i', i -= s);
x0 = null
}
};
Das Ergebnis beim Ziehen nach links/rechts ist unten zu sehen.

Das Obige ist das erwartete Ergebnis und das Ergebnis, das wir in Chrome für ein wenig Ziehen und in Firefox erhalten. Edge navigiert jedoch beim Ziehen nach links oder rechts vorwärts und rückwärts, was Chrome ebenfalls bei etwas mehr Ziehen tut.

Um dies zu überschreiben, müssen wir einen "touchmove"-Ereignis-Listener hinzufügen.
_C.addEventListener('touchmove', e => {e.preventDefault()}, false)
Okay, wir haben jetzt etwas Funktionierendes in allen Browsern, aber es sieht noch nicht so aus, wie wir es wirklich wollen ... noch!
Flüssige Bewegung
Der einfachste Weg, dem Ziel näher zu kommen, ist das Hinzufügen einer transition.
.container {
/* same styles as before */
transition: transform .5s ease-out;
}
Und hier ist es: ein sehr einfacher Wisch-Effekt in etwa 25 Zeilen JavaScript und etwa 25 Zeilen CSS.
Leider gibt es einen Edge-Bug, der dazu führt, dass jede transition zu einer auf CSS-Variablen basierenden calc()-Translation fehlschlägt. Ugh, ich schätze, wir müssen Edge vorerst vergessen.
Das Ganze verfeinern
Mit all den coolen Wisch-Effekten da draußen ist das, was wir bisher haben, nicht ganz ausreichend. Mal sehen, welche Verbesserungen wir machen können.
Bessere visuelle Hinweise während des Ziehens
Zuerst passiert nichts, während wir ziehen, alle Aktionen folgen dem "touchend"- (oder "mouseup"-) Ereignis. Also, während wir ziehen, haben wir keine Ahnung, was als nächstes passieren wird. Gibt es ein nächstes Bild, zu dem in der gewünschten Richtung gewechselt werden kann? Oder haben wir das Ende der Fahnenstange erreicht und es wird nichts passieren?
Um uns darum zu kümmern, passen wir den Translationsbetrag etwas an, indem wir eine CSS-Variable --tx hinzufügen, die ursprünglich 0px ist.
transform: translate(calc(var(--i, 0)/var(--n)*-100% + var(--tx, 0px)))
Wir verwenden zwei weitere Ereignis-Listener: einen für "touchmove" und einen für "mousemove". Beachten Sie, dass wir die Rückwärts- und Vorwärtsnavigation in Chrome bereits mit dem "touchmove"-Listener verhindert haben.
function drag(e) { e.preventDefault() };
_C.addEventListener('mousemove', drag, false);
_C.addEventListener('touchmove', drag, false);
Jetzt füllen wir die drag()-Funktion! Wenn wir die lock()-Aktion durchgeführt haben, lesen wir die aktuelle x-Koordinate, berechnen die Differenz dx zwischen dieser Koordinate und der anfänglichen x0 und setzen --tx auf diesen Wert (der ein Pixelwert ist).
function drag(e) {
e.preventDefault();
if(x0 || x0 === 0)
_C.style.setProperty('--tx', `${Math.round(unify(e).clientX - x0)}px`)
};
Wir müssen auch sicherstellen, dass --tx am Ende auf 0px zurückgesetzt wird und die transition für die Dauer des Ziehens entfernt wird. Um dies zu erleichtern, verschieben wir die transition-Deklaration in eine .smooth-Klasse.
.smooth { transition: transform .5s ease-out; }
In der lock()-Funktion entfernen wir diese Klasse vom .container (wir fügen sie am Ende bei "touchend" und "mouseup" wieder hinzu) und setzen auch eine boolesche Variable locked, damit wir nicht ständig die x0 || x0 === 0-Prüfung durchführen müssen. Stattdessen verwenden wir die locked-Variable für die Prüfungen.
let locked = false;
function lock(e) {
x0 = unify(e).clientX;
_C.classList.toggle('smooth', !(locked = true))
};
function drag(e) {
e.preventDefault();
if(locked) { /* same as before */ }
};
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0, s = Math.sign(dx);
if((i > 0 || s < 0) && (i < N - 1 || s > 0))
_C.style.setProperty('--i', i -= s);
_C.style.setProperty('--tx', '0px');
_C.classList.toggle('smooth', !(locked = false));
x0 = null
}
};
Das Ergebnis ist unten zu sehen. Während wir noch ziehen, haben wir jetzt eine visuelle Anzeige dessen, was als nächstes passieren wird.
transition-duration reparieren
Zu diesem Zeitpunkt verwenden wir immer die gleiche transition-duration, unabhängig davon, wie viel der width eines Bildes wir nach dem Ziehen noch übersetzen müssen. Das können wir auf ziemlich einfache Weise beheben, indem wir einen Faktor f einführen, den wir auch als CSS-Variable festlegen, um uns bei der Berechnung der tatsächlichen Animationsdauer zu helfen.
.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out; }
Im JavaScript ermitteln wir die width eines Bildes (aktualisiert bei "resize") und berechnen, für welchen Bruchteil davon wir horizontal gezogen haben.
let w;
function size() { w = window.innerWidth };
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0, s = Math.sign(dx),
f = +(s*dx/w).toFixed(2);
if((i > 0 || s < 0) && (i < N - 1 || s > 0)) {
_C.style.setProperty('--i', i -= s);
f = 1 - f
}
_C.style.setProperty('--tx', '0px');
_C.style.setProperty('--f', f);
_C.classList.toggle('smooth', !(locked = false));
x0 = null
}
};
size();
addEventListener('resize', size, false);
Dies ergibt jetzt ein besseres Ergebnis.
Zurückspringen bei unzureichendem Ziehen
Nehmen wir an, wir wollen nicht zum nächsten Bild wechseln, wenn wir nur ein wenig unter einem bestimmten Schwellenwert ziehen. Denn jetzt bedeutet eine 1px-Differenz während des Ziehens, dass wir zum nächsten Bild wechseln, und das fühlt sich etwas unnatürlich an.
Um dies zu beheben, legen wir einen Schwellenwert fest, sagen wir 20% der width eines Bildes.
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0, s = Math.sign(dx),
f = +(s*dx/w).toFixed(2);
if((i > 0 || s < 0) && (i < N - 1 || s > 0) && f > .2) {
/* same as before */
}
/* same as before */
}
};
Das Ergebnis ist unten zu sehen.
Vielleicht einen Sprung hinzufügen?
Das ist etwas, von dem ich nicht sicher bin, ob es eine gute Idee war, aber ich musste es sowieso ausprobieren: Ändern Sie die Timing-Funktion, um einen Sprung einzuführen. Nach einigem Ziehen an den Griffen auf cubic-bezier.com kam ich zu einem Ergebnis, das vielversprechend aussah.
![Animated gif. Shows the graphical representation of the cubic Bézier curve, with start point at (0, 0), end point at (1, 1) and control points at (1, 1.59) and (.61, .74), the progression on the [0, 1] interval being a function of time in the [0, 1] interval. Also illustrates how the transition function given by this cubic Bézier curve looks when applied on a translation compared to a plain ease-out.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/03/bounce_css.gif?ssl=1)
ease-out aussieht.transition: transform calc(var(--f)*.5s) cubic-bezier(1, 1.59, .61, .74);
Wie wäre es mit dem JavaScript-Weg?
Wir könnten einen besseren Grad an Kontrolle über natürlichere und komplexere Sprünge erreichen, indem wir den JavaScript-Weg für die Transition wählen. Das würde auch Edge-Unterstützung bieten.
Wir beginnen damit, die transition und die CSS-Variablen --tx und --f zu entfernen. Dies reduziert unser transform auf das, was es ursprünglich war.
transform: translate(calc(var(--i, 0)/var(--n)*-100%));
Der obige Code bedeutet auch, dass --i nicht mehr unbedingt eine Ganzzahl ist. Während es eine Ganzzahl bleibt, wenn wir ein einzelnes Bild vollständig im Blick haben, ist das nicht mehr der Fall, während wir ziehen oder während der Bewegung nach Auslösen der "touchend"- oder "mouseup"-Ereignisse.

--i 0. Während wir das zweite vollständig im Blick haben, ist --i 1. Wenn wir uns auf halbem Weg zwischen dem ersten und dem zweiten befinden, ist --i .5. Wenn wir ein Viertel des ersten und drei Viertel des zweiten im Blick haben, ist --i .75.Wir aktualisieren dann das JavaScript, um die Code-Teile zu ersetzen, in denen wir diese CSS-Variablen aktualisiert haben. Zuerst kümmern wir uns um die lock()-Funktion, wo wir das Umschalten der .smooth-Klasse aufgeben, und um die drag()-Funktion, wo wir die Aktualisierung der verworfenen --tx-Variable durch die Aktualisierung von --i ersetzen, das, wie bereits erwähnt, keine Ganzzahl mehr sein muss.
function lock(e) {
x0 = unify(e).clientX;
locked = true
};
function drag(e) {
e.preventDefault();
if(locked) {
let dx = unify(e).clientX - x0,
f = +(dx/w).toFixed(2);
_C.style.setProperty('--i', i - f)
}
};
Bevor wir auch die move()-Funktion aktualisieren, führen wir zwei neue Variablen ein: ini und fin. Diese repräsentieren den Anfangswert, den wir --i zu Beginn der Animation zuweisen, und den Endwert, den wir derselben Variablen am Ende der Animation zuweisen. Wir erstellen auch eine Animationsfunktion ani().
let ini, fin;
function ani() {};
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0,
s = Math.sign(dx),
f = +(s*dx/w).toFixed(2);
ini = i - s*f;
if((i > 0 || s < 0) && (i < N - 1 || s > 0) && f > .2) {
i -= s;
f = 1 - f
}
fin = i;
ani();
x0 = null;
locked = false;
}
};
Das ist nicht viel anders als der bisherige Code. Geändert hat sich, dass wir in dieser Funktion keine CSS-Variablen mehr setzen, sondern stattdessen die JavaScript-Variablen ini und fin setzen und die Animationsfunktion ani() aufrufen.
ini ist der Anfangswert, den wir --i zu Beginn der Animation zuweisen, die durch das "touchend"/"mouseup"-Ereignis ausgelöst wird. Dieser ergibt sich aus der aktuellen Position, die wir haben, wenn eines dieser beiden Ereignisse eintritt.
fin ist der Endwert, den wir --i am Ende derselben Animation zuweisen. Dies ist immer ein ganzzahliger Wert, da wir immer mit einem vollständig sichtbaren Bild enden, sodass fin und --i der Index dieses Bildes sind. Dies ist das nächste Bild in der gewünschten Richtung, wenn wir genug gezogen haben (f > .2) und wenn es ein nächstes Bild in der gewünschten Richtung gibt ((i > 0 || s < 0) && (i < N - 1 || s > 0)). In diesem Fall aktualisieren wir auch die JavaScript-Variable, die den aktuellen Bildindex (i) und den relativen Abstand dazu (f) speichert. Andernfalls ist es dasselbe Bild, sodass i und f nicht aktualisiert werden müssen.
Kommen wir nun zur ani()-Funktion. Wir beginnen mit einer vereinfachten linearen Version, die eine Richtungsänderung auslässt.
const NF = 30;
let rID = null;
function stopAni() {
cancelAnimationFrame(rID);
rID = null
};
function ani(cf = 0) {
_C.style.setProperty('--i', ini + (fin - ini)*cf/NF);
if(cf === NF) {
stopAni();
return
}
rID = requestAnimationFrame(ani.bind(this, ++cf))
};
Die Hauptidee hierbei ist, dass der Übergang vom Anfangswert ini zum Endwert fin über eine Gesamtzahl von Frames NF erfolgt. Jedes Mal, wenn wir die ani()-Funktion aufrufen, berechnen wir den Fortschritt als Verhältnis zwischen dem aktuellen Frame-Index cf und der Gesamtzahl der Frames NF. Dies ist immer eine Zahl zwischen 0 und 1 (oder Sie können sie als Prozentsatz betrachten, von 0% bis 100%). Wir verwenden diesen Fortschrittswert dann, um den aktuellen Wert von --i zu erhalten und ihn im Stilattribut unseres Containers _C festzulegen. Wenn wir den Endzustand erreicht haben (der aktuelle Frame-Index cf entspricht der Gesamtzahl der Frames NF), verlassen wir die Animationsschleife. Andernfalls erhöhen wir einfach den aktuellen Frame-Index cf und rufen ani() erneut auf.
Zu diesem Zeitpunkt haben wir eine funktionierende Demo mit einer linearen JavaScript-Transition.
Dies hat jedoch das Problem, das wir ursprünglich im CSS-Fall hatten: Unabhängig von der Distanz müssen wir beim Loslassen ("touchend" / "mouseup") unser Element glatt verschieben, und die Dauer ist immer gleich, da wir immer über die gleiche Anzahl von Frames NF animieren.
Lasst uns das beheben!
Um dies zu erreichen, führen wir eine weitere Variable anf ein, in der wir die tatsächliche Anzahl der verwendeten Frames speichern und deren Wert wir in der move()-Funktion berechnen, bevor wir die Animationsfunktion ani() aufrufen.
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0,
s = Math.sign(dx),
f = +(s*dx/w).toFixed(2);
/* same as before */
anf = Math.round(f*NF);
ani();
/* same as before */
}
};
Wir müssen auch NF durch anf in der Animationsfunktion ani() ersetzen.
function ani(cf = 0) {
_C.style.setProperty('--i', ini + (fin - ini)*cf/anf);
if(cf === anf) { /* same as before */ }
/* same as before */
};
Damit haben wir das Timing-Problem behoben!
Okay, aber eine lineare Timing-Funktion ist nicht gerade aufregend.
Wir könnten die JavaScript-Äquivalente von CSS-Timing-Funktionen wie ease-in, ease-out oder ease-in-out ausprobieren und sehen, wie sie sich vergleichen. Ich habe bereits im zuvor verlinkten Artikel im Detail erklärt, wie man diese erhält, daher werde ich nicht noch einmal darauf eingehen und das Objekt mit allen davon einfach in den Code fallen lassen.
const TFN = {
'linear': function(k) { return k },
'ease-in': function(k, e = 1.675) {
return Math.pow(k, e)
},
'ease-out': function(k, e = 1.675) {
return 1 - Math.pow(1 - k, e)
},
'ease-in-out': function(k) {
return .5*(Math.sin((k - .5)*Math.PI) + 1)
}
};
Der k-Wert ist der Fortschritt, der das Verhältnis zwischen dem aktuellen Frame-Index cf und der tatsächlichen Anzahl von Frames, über die die Transition läuft, anf ist. Das bedeutet, wir modifizieren die ani()-Funktion ein wenig, wenn wir zum Beispiel die Option ease-out verwenden wollen.
function ani(cf = 0) {
_C.style.setProperty('--i', ini + (fin - ini)*TFN['ease-out'](cf/anf));
/* same as before */
};
ease-out JavaScript-Transition (Live-Demo).Wir könnten die Dinge auch interessanter machen, indem wir die Art von hüpfender Timing-Funktion verwenden, die CSS nicht bieten kann. Zum Beispiel etwas Ähnliches wie das, was in der unten gezeigten Demo illustriert wird (klicken, um eine Transition auszulösen).
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Grafik dafür wäre der des easeOutBounce Timing-Funktions-Typs von easings.net ähnlich.

Der Prozess, diese Art von Timing-Funktion zu erhalten, ähnelt dem Erhalt der JavaScript-Version der CSS ease-in-out (wiederum beschrieben im zuvor verlinkten Artikel über die Emulation von CSS-Timing-Funktionen mit JavaScript).
Wir beginnen mit der Kosinusfunktion im Intervall [0, 90°] (oder [0, π/2] in Radiant) für keinen Sprung, [0, 270°] ([0, 3·π/2]) für 1 Sprung, [0, 450°] ([0, 5·π/2]) für 2 Sprünge und so weiter ... generell ist es das Intervall [0, (n + ½)·180°] ([0, (n + ½)·π]) für n Sprünge.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Eingabe dieser cos(k)-Funktion liegt im Intervall [0, 450°], während ihre Ausgabe im Intervall [-1, 1] liegt. Wir wollen jedoch eine Funktion, deren Definitionsbereich das Intervall [0, 1] ist und deren Wertebereich ebenfalls das Intervall [0, 1] ist.
Wir können den Wertebereich auf das Intervall [0, 1] beschränken, indem wir nur den Absolutwert |cos(k)| nehmen.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Während wir das gewünschte Intervall für den Wertebereich erhalten haben, wollen wir, dass der Wert dieser Funktion bei 0 0 ist und ihr Wert am anderen Ende des Intervalls 1 ist. Aktuell ist es umgekehrt, aber das können wir beheben, indem wir unsere Funktion zu 1 - |cos(k)| ändern.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Nun können wir den Definitionsbereich vom Intervall [0, (n + ½)·180°] auf das Intervall [0, 1] beschränken. Um dies zu tun, ändern wir unsere Funktion zu 1 - |cos(k·(n + ½)·180°)|.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Dies gibt uns sowohl den gewünschten Definitionsbereich als auch den Wertebereich, aber wir haben immer noch einige Probleme.
Erstens haben alle unsere Sprünge die gleiche Höhe, aber wir möchten, dass ihre Höhe abnimmt, wenn k von 0 bis 1 ansteigt. Unsere Lösung in diesem Fall ist, den Kosinus mit 1 - k (oder mit einer Potenz von 1 - k für eine nichtlineare Amplitudenabnahme) zu multiplizieren. Die interaktive Demo unten zeigt, wie sich diese Amplitude für verschiedene Exponenten a ändert und wie dies die bisherige Funktion beeinflusst.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Zweitens nehmen alle Sprünge die gleiche Zeit in Anspruch, obwohl ihre Amplituden weiter abnehmen. Die erste Idee hier ist, eine Potenz von k anstelle von nur k in der Kosinusfunktion zu verwenden. Dies macht die Dinge seltsam, da der Kosinus nicht mehr in gleichen Intervallen 0 erreicht, was bedeutet, dass wir nicht immer f(1) = 1 erhalten, was wir jedoch immer von einer Timing-Funktion benötigen, die wir tatsächlich verwenden wollen. Für etwas wie a = 2.75, n = 3 und b = 1.5 erhalten wir jedoch ein zufriedenstellendes Ergebnis, also belassen wir es dabei, auch wenn es für bessere Kontrolle optimiert werden könnte.

Dies ist die Funktion, die wir im JavaScript ausprobieren, wenn wir möchten, dass etwas springt.
const TFN = {
/* the other function we had before */
'bounce-out': function(k, n = 3, a = 2.75, b = 1.5) {
return 1 - Math.pow(1 - k, a)*Math.abs(Math.cos(Math.pow(k, b)*(n + .5)*Math.PI))
}
};
Hmm, das scheint in der Praxis etwas zu extrem.
Vielleicht könnten wir n vom Betrag der Verdrängung abhängig machen, die wir ab dem Zeitpunkt des Loslassens noch ausführen müssen. Wir machen daraus eine Variable, die wir dann in der move()-Funktion festlegen, bevor wir die Animationsfunktion ani() aufrufen.
const TFN = {
/* the other function we had before */
'bounce-out': function(k, a = 2.75, b = 1.5) {
return 1 - Math.pow(1 - k, a)*Math.abs(Math.cos(Math.pow(k, b)*(n + .5)*Math.PI))
}
};
var n;
function move(e) {
if(locked) {
let dx = unify(e).clientX - x0,
s = Math.sign(dx),
f = +(s*dx/w).toFixed(2);
/* same as before */
n = 2 + Math.round(f)
ani();
/* same as before */
}
};
Dies ergibt unser Endergebnis.
Es gibt definitiv noch Raum für Verbesserungen, aber ich habe kein Gespür dafür, was eine gute Animation ausmacht, also belasse ich es dabei. So wie es ist, ist es jetzt browserübergreifend funktionsfähig (ohne die Edge-Probleme, die die Version mit CSS-Transition hat) und ziemlich flexibel.
Lohnt es sich, Pointer Events anstelle von Touch Events zu verwenden?
Wahrscheinlich. Aber ich habe noch einen langen Weg vor mir, bevor ich Pointer Events verstehe.
Sie sind im Wesentlichen Mausereignisse mit zusätzlichen Attributen (da sie Mausereignisse erweitern). Dies würde es ermöglichen, den Schieberegler gleich (oder unterschiedlich, da Sie unterscheiden können) für Touch, Stift und sogar Maus selbst mit demselben Code zu haben.
Hier ist ein guter Artikel darüber, wie man Pointer Events verwendet. https://developers.google.com/web/fundamentals/design-and-ux/input/touch/#add_event_listeners
Fügen Sie Ihren
mouseup-Listener möglicherweise auch demwindowhinzu. Wenn Ihre Maus während des Klickens und Ziehens das Browserfenster verlässt, können Sie die Maustaste loslassen und der Container verpasst das Ereignis. Listener, die anwindowangehängt sind, werden auch dann aufgerufen, wenn diesesmouseupaußerhalb des Fensters auftritt.Hoppla... Ich dachte immer,
addEventListenersei dasselbe wiewindow.addEventListener...Das ist verdammt cool, aber wie zum Teufel erinnert man sich, was jede der einbuchstabigen Variablen tut? Ich habe den Überblick verloren, als ich das gelesen habe :\
Namensgebung ist immer schwierig. Eine Konvention mag für manche einfacher sein als für andere, aber glücklicherweise hängt das Ergebnis nicht von einer Konvention über eine andere ab. :)
Das tue ich nicht, weil ich sie mir nicht merken muss, ich muss sie nur aus dem Kontext verstehen. Deshalb benutze ich einzelne Buchstaben. Wenn ich längere Variablennamen verwenden würde, hätte ich Schwierigkeiten, sie zu verstehen und sie dem Konzept zuzuordnen, das sie definieren. Ich weiß nicht, wie ich das erklären soll, aber ich denke nicht wirklich in Worten, Sätzen.
Wenn ich sehe, dass eine Variable
aauf einen Grad- oder Radianwert gesetzt wird, visualisiere ich sofort eine geometrische Zeichnung eines Winkels. Wenn ich eine VariableangleForBlaBlasehe, gerate ich in Panik, weil es zu viele Buchstaben gibt und ich nicht verstehe, was diese Sache aussagt, was genau ist darin relevant.Ich weiß nicht, es ist einfach die Art und Weise, wie mein Gehirn funktioniert. Deshalb habe ich Schwierigkeiten beim Lesen und Schreiben. Deshalb verwende ich so viele visuelle Demos in meinen Artikeln. Ich habe absolut keine Ahnung, wie ich manche Dinge mit Worten erklären könnte. Oder warum ich Erklärungen nicht verstehen würde, wenn sie in Worten und nicht visuell gegeben würden.
Oh verdammt, das ist ein verrückter Grund. Ich schätze, das ist etwas, bei dem wir uns niemals einig sein könnten, basierend darauf, wie unsere Gehirne funktionieren. Ich sage nicht, dass es falsch ist, überhaupt nicht, ich habe Schwierigkeiten, es zu verstehen, aus genau dem Grund, warum Sie es einfach finden. Ich kann den Kontext nicht leicht ableiten.
Cool, aber warum das? Es gibt Bibliotheken, die das bereits für Sie erledigen. Das ist so viel Programmierzeit für eine so einfache Aktion.
Wegen des sehr berechtigten Punkts hier (#1): https://www.leaseweb.com/labs/2013/07/10-very-good-reasons-to-stop-using-javascript/
Haben Sie die Menge an Code in der eigentlichen Demo gesehen? Ernsthafte Frage.
Ich habe vielleicht einen sehr langen Artikel darüber geschrieben, aber das tatsächliche Programmieren des Swipes hat nur wenig Zeit gekostet, viel weniger, als ich für die Implementierung mit einer Bibliothek aufgewendet hätte. Es hat mich weniger als eine halbe Stunde gekostet, den JS-Teil für meinen anfänglichen Swipe zu machen, und die Hälfte dieser Zeit wurde für Verfeinerung und Anpassung aufgewendet.
Die Verwendung einer Bibliothek hätte bedeutet, diese halbe Stunde nur damit zu verbringen, eine Bibliothek zu finden und zu entscheiden. Es hätte mich mindestens einen Tag gekostet, zu verstehen, wie die Bibliothek zu verwenden ist. Ich weiß nicht, wie es bei anderen ist, aber das ist für mich das größte Problem, wenn es um Bibliotheken geht – ich habe einfach unheimlich Schwierigkeiten zu verstehen, wie man sie benutzt, *wenn* ich sie überhaupt verstehen kann. Selbst wenn sie hervorragende Dokumentationen haben. Selbst wenn die Verwendung der Bibliothek bedeutet hätte, dass ich nur zwei Zeilen JS schreibe, hätte ich trotzdem viel mehr Zeit dafür aufgewendet.
Vielleicht eine dumme Frage, aber wie setzen Sie die Variable
--iam Anfang?Diese Codezeile:
transform: translate(calc(var(--i)/var(--n)*-100%));Das ist eigentlich eine gute Frage.
Das tue ich nicht. Nicht aus dem CSS und anfangs nicht einmal aus dem JS. Ich setze es nur aus dem JS, wenn ich zu einem anderen Bild wechsle. Wenn es am Anfang gar nicht gesetzt ist, ist der Effekt derselbe, als wäre es auf
0gesetzt. Da es keinen Unterschied macht, mache ich es mir bequem und setze es einfach nicht.Guter Ansatz. Die Animationsfunktionen sind immer etwas einschüchternd zu lesen, vielleicht weil ich keinen mathematischen Hintergrund habe ;-) Mehr menschenlesbare Variablennamen wären hilfreich.
Ich habe vor einiger Zeit einen ähnlichen Karussell gebaut, der keine CSS-Variablen verwendet. Stattdessen animiert sich jedes Element im Karussell selbst (mittels
TranslateX(-100%)). Der Vorteil ist, dass Sie nicht die Breite des Containers, die Breite jedes Bildes und die Anzahl der Elemente im Karussell berechnen müssen. Wenn Sie neugierig sind (natürlich ist jedes Feedback willkommen)https://github.com/reinoute/progressive-carousel
Toller Artikel, ich liebe es, wie Sie durch die Implementierung fortschreiten. Das muss ziemlich viel Zeit zum Schreiben gekostet haben!
Ich mag es auch, wie Sie ruhig diese interaktiven Diagramme hinzufügen und sie dann mehr Code haben als Ihr Slider
Wie auch immer, das Einzige, was ich nicht verstehe, ist, warum Sie calc() verwenden, wenn Sie alle Werte einfach in JS berechnen können? Dann hätten Sie das Übergangsproblem mit Edge nicht?
Stimmt, auf diese Weise hätte ich das Problem nicht. Ich hasse es einfach, Werte in JS zu lesen und zu berechnen. Numerische Berechnungen im Allgemeinen.
Aber ja, das ist definitiv eine praktikable Option, besonders in der Praxis.
Was die Grafiken/Hilfsdemos angeht ... sie *sind* im Hintergrund komplexer als die Swipe-Demo und haben länger gedauert. Und manchmal stoße ich mit den interaktiven Hilfsmitteln an meine Grenzen.
Danke für das Teilen eines so erstaunlichen Artikels, wirklich informativ
Es war ein erstaunliches Tutorial! Vielen Dank!!
Aber bitte versuchen Sie sich daran zu erinnern, dass wir nicht mehr in C sind. Wir können 2018 lange Namen für Variablen verwenden :)
Es wäre einfach für N00bs zu lesen und zu verstehen ;)
Dieses Tutorial ist so großartig!!! Schritt für Schritt, so klar. Ich kann ihnen leicht folgen! Danke
Toller Artikel! CSS (Tricks) zuerst, JS-Verbesserungen später, wie mein http://radogado.github.io/native-slider/
Mit freundlichen Grüßen.
Hallo! Tolle Beispiele. Ich benutze heute eine andere, aber sie funktioniert nicht so, wie ich möchte (automatische Höhe). Meine wird stattdessen die Höhe der größten Karte verwenden. Könnte ich Ihre verwenden und irgendwie auch die JS-Datei bereinigen, um nur die Einstellungen für die automatische Höhe irgendwie aufzunehmen?
Hallo Adrian, danke. Automatische Höhe ist nur eine Option und ich fürchte, das gesamte JS ist erforderlich. Dieses Tool ist ein Teil von https://github.com/radogado/natuive
Viel Glück!