Benutzerdefinierte Bildlaufleisten sind extrem selten. Das liegt vor allem daran, dass sie eine der verbleibenden Elemente im Web sind, die so gut wie nicht stilbar sind. Sie können Ihre eigenen mit JavaScript erstellen, aber das ist teuer, hat eine geringe Wiedergabetreue und kann zu Verzögerungen führen. In diesem Artikel verwenden wir einige unkonventionelle CSS-Matrizen, um einen benutzerdefinierten Scroller zu erstellen, der beim Scrollen kein JavaScript, sondern nur etwas Einrichtungscode erfordert.
Kurzfassung
Die kleinen Dinge sind dir nicht wichtig? Sie möchten sich nur die Nyan-Katze-Demo ansehen und die Bibliothek herunterladen? Den Democode finden Sie in unserem GitHub-Repository.
LAM;WRA (Lang und mathematisch; wird trotzdem gelesen)
Vor einiger Zeit haben wir einen Paralax-Scroller erstellt. Haben Sie diesen Artikel gelesen? Es ist wirklich gut und lohnt sich!). Durch das Zurückschieben von Elementen mithilfe von CSS-3D-Transformationen bewegten sich die Elemente langsamer als die tatsächliche Scrollgeschwindigkeit.
Zusammenfassung
Sehen wir uns zuerst noch einmal an, wie der Paralax-Scroller funktioniert hat.
Wie in der Animation zu sehen, haben wir den Parallaxeneffekt erzielt, indem wir Elemente im 3D-Raum entlang der Z‑Achse „nach hinten“ geschoben haben. Das Scrollen eines Dokuments ist im Grunde eine Verschiebung entlang der Y-Achse. Wenn wir also beispielsweise 100 Pixel nach unten scrollen, wird jedes Element um 100 Pixel nach oben verschoben. Das gilt für alle Elemente, auch für die, die „weiter hinten“ sind. Da sie jedoch weiter von der Kamera entfernt sind, beträgt ihre beobachtete Bewegung auf dem Bildschirm weniger als 100 Pixel, was den gewünschten Parallaxeneffekt ergibt.
Wenn ein Element nach hinten verschoben wird, erscheint es natürlich auch kleiner. Das korrigieren wir, indem wir das Element wieder vergrößern. Die genauen Berechnungen haben wir beim Erstellen des Parallax-Scrollers herausgefunden. Ich werde also nicht alle Details wiederholen.
Schritt 0: Was möchten wir tun?
Bildlaufleisten Das ist es, was wir bauen werden. Aber haben Sie jemals darüber nachgedacht, was sie tun? Habe ich auf jeden Fall nicht. Bildlaufleisten geben Aufschluss darüber, wie viel des verfügbaren Inhalts derzeit sichtbar ist und wie viel Fortschritt Sie als Leser gemacht haben. Wenn Sie nach unten scrollen, zeigt auch die Bildlaufleiste an, dass Sie Fortschritte machen. Wenn alle Inhalte in den Darstellungsbereich passen, ist die Bildlaufleiste normalerweise ausgeblendet. Wenn der Inhalt doppelt so hoch wie der Darstellungsbereich ist, nimmt der Bildlaufbalken die Hälfte der Höhe des Darstellungsbereichs ein. Bei Inhalten mit der dreifachen Höhe des Darstellungsbereichs wird die Bildlaufleiste auf ein Drittel des Darstellungsbereichs skaliert usw. Sie sehen das Muster. Anstatt zu scrollen, können Sie auch auf die Bildlaufleiste klicken und sie ziehen, um sich schneller auf der Website zu bewegen. Das ist eine überraschende Menge an Verhalten für ein unscheinbares Element wie dieses. Lass uns einen Schritt nach dem anderen machen.
Schritt 1: Rückwärtsgang einlegen
Okay, wir können Elemente mit CSS-3D-Transformationen langsamer als die Scrollgeschwindigkeit bewegen, wie im Artikel zum Parallax-Scrolling beschrieben. Können wir die Richtung auch umkehren? Das ist möglich und so können wir eine scrollbare Leiste erstellen, die perfekt zu den Frames passt. Um zu verstehen, wie das funktioniert, müssen wir zunächst einige CSS-3D-Grundlagen behandeln.
Wenn Sie eine perspektivische Projektion im mathematischen Sinne erhalten möchten, verwenden Sie am besten homogene Koordinaten. Ich gehe nicht näher darauf ein, was sie sind und warum sie funktionieren, aber Sie können sie sich als 3D-Koordinaten mit einer zusätzlichen vierten Koordinate namens w vorstellen. Diese Koordinate sollte 1 sein, es sei denn, Sie möchten eine perspektivische Verzerrung haben. Wir müssen uns keine Gedanken über die Details von w machen, da wir keinen anderen Wert als 1 verwenden werden. Daher sind alle Punkte ab sofort vierdimensionale Vektoren [x, y, z, w=1] und Matrizen müssen ebenfalls 4 × 4 sein.
Eine Möglichkeit, zu sehen, dass in CSS homogene Koordinaten verwendet werden, ist, wenn Sie Ihre eigenen 4 × 4-Matrizen in einer Transform-Eigenschaft mit der Funktion matrix3d()
definieren. matrix3d
nimmt 16 Argumente an (da die Matrix 4 × 4 Elemente hat), wobei eine Spalte nach der anderen angegeben wird. Mit dieser Funktion können wir also Drehungen, Verschiebungen usw. manuell angeben. Außerdem können wir damit die w-Koordinate ändern.
Bevor wir matrix3d()
verwenden können, benötigen wir einen 3D-Kontext. Denn ohne 3D-Kontext gäbe es keine perspektivische Verzerrung und keine Notwendigkeit für homogene Koordinaten. Um einen 3D-Kontext zu erstellen, benötigen wir einen Container mit einer perspective
und einigen Elementen, die wir im neu erstellten 3D-Raum transformieren können. Beispiel:
Die Elemente in einem perspektivischen Container werden von der CSS-Engine so verarbeitet:
- Wandeln Sie jede Ecke (Knotenpunkt) eines Elements in homogene Koordinaten
[x,y,z,w]
um, bezogen auf den perspektivischen Container. - Wenden Sie alle Transformationen des Elements als Matrizen von rechts nach links an.
- Wenn das perspektivische Element scrollbar ist, wenden Sie eine Scrollmatrix an.
- Wenden Sie die Perspektivmatrix an.
Die Scrollmatrix ist eine Verschiebung entlang der Y-Achse. Wenn wir 400 px nach unten scrollen, müssen alle Elemente um 400 px nach oben verschoben werden. Die Perspektivmatrix ist eine Matrix, die Punkte je weiter sie im 3D-Raum nach hinten liegen, näher an den Fluchtpunkt heranzieht. Dadurch wirken Objekte, die weiter hinten sind, kleiner und bewegen sich beim Schwenken langsamer. Wenn ein Element also nach hinten verschoben wird, bewegt es sich bei einer Verschiebung von 400 Pixeln nur um 300 Pixel auf dem Bildschirm.
Wenn Sie alle Details erfahren möchten, sollten Sie die Spezifikation zum Transformierungs-Rendering-Modell des Preisvergleichsportals lesen. Für diesen Artikel habe ich den Algorithmus jedoch vereinfacht.
Unser Feld befindet sich in einem perspektivischen Container mit dem Wert „p“ für das perspective
-Attribut. Angenommen, der Container ist scrollbar und wird um n Pixel nach unten gescrollt.
Die erste Matrix ist die perspektivische Matrix, die zweite Matrix die Scroll-Matrix. Zur Wiederholung: Die Scrollmatrix sorgt dafür, dass sich ein Element nach oben bewegt, wenn wir nach unten scrollen, daher das negative Vorzeichen.
Bei unserer Bildlaufleiste möchten wir jedoch das Gegenteil: Wir möchten, dass sich das Element nach unten bewegt, wenn wir nach unten scrollen. Hier können wir einen Trick anwenden: Wir kehren die w-Koordinate der Ecken unseres Quadrats um. Wenn die w-Koordinate -1 ist, werden alle Verschiebungen in die entgegengesetzte Richtung ausgeführt. Wie gehen wir vor? Die CSS-Engine kümmert sich um die Umwandlung der Ecken unseres Quadrats in homogene Koordinaten und setzt w auf 1. Jetzt ist es an der Zeit, dass matrix3d()
glänzt!
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
Diese Matrix tut nichts anderes, als w zu negieren. Wenn die CSS-Engine also jede Ecke in einen Vektor der Form [x,y,z,1]
umgewandelt hat, wandelt die Matrix ihn in [x,y,z,-1]
um.
Ich habe einen Zwischenschritt aufgeführt, um die Wirkung unserer Elementtransformationsmatrix zu veranschaulichen. Wenn Sie mit Matrizenmathematik nicht vertraut sind, ist das in Ordnung. Der Aha-Moment ist, dass wir in der letzten Zeile den Scroll-Offset n unserer y-Koordinate hinzufügen, anstatt ihn abzuziehen. Das Element wird nach unten verschoben, wenn wir nach unten scrollen.
Wenn wir diese Matrix jedoch einfach in unser Beispiel einfügen, wird das Element nicht angezeigt. Das liegt daran, dass gemäß der CSS-Spezifikation jeder Scheitelpunkt mit w < 0 das Rendern des Elements blockiert. Da unsere Z‑Koordinate derzeit 0 und p 1 ist, ist w −1.
Glücklicherweise können wir den Wert von z auswählen. Damit wir am Ende w=1 erhalten, müssen wir z = −2 festlegen.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
Und siehe da, unsere Box ist zurück!
Schritt 2: Verschieben
Jetzt ist unser Feld da und sieht genauso aus wie ohne Transformationen. Der perspektivische Container ist derzeit nicht scrollbar, sodass wir ihn nicht sehen können. Wir wissen jedoch, dass sich unser Element beim Scrollen in die andere Richtung bewegt. Lassen Sie uns also den Container scrollen. Wir können einfach ein Abstandselement hinzufügen, das Platz einnimmt:
<div class="container">
<div class="box"></div>
<span class="spacer"></span>
</div>
<style>
/* … all the styles from the previous example … */
.container {
overflow: scroll;
}
.spacer {
display: block;
height: 500px;
}
</style>
Scrollen Sie jetzt im Feld. Das rote Feld wird nach unten verschoben.
Schritt 3: Größe festlegen
Wir haben ein Element, das nach unten bewegt wird, wenn auf der Seite nach unten gescrollt wird. Das ist der schwierige Teil auf dem Weg zu gehen. Jetzt müssen wir ihm ein scrollbares Aussehen geben und es etwas interaktiver gestalten.
Eine Bildlaufleiste besteht in der Regel aus einem „Schieberegler“ und einem „Track“, wobei der Track nicht immer sichtbar ist. Die Höhe des Vorschaubilds ist direkt proportional dazu, wie viel vom Inhalt sichtbar ist.
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
ist die Höhe des scrollbaren Elements, während scroller.scrollHeight
die Gesamthöhe des scrollbaren Inhalts ist.
scrollerHeight/scroller.scrollHeight
ist der sichtbare Anteil des Inhalts. Das Verhältnis des vertikalen Bereichs, den der Vorschaubereich einnimmt, sollte dem Verhältnis der sichtbaren Inhalte entsprechen:
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
Die Größe des Vorschaubildes sieht gut aus, aber es bewegt sich viel zu schnell. Hier können wir unsere Technik aus dem Parallax-Scroller übernehmen. Wenn wir das Element weiter nach hinten verschieben, bewegt es sich beim Scrollen langsamer. Wir können die Größe korrigieren, indem wir sie vergrößern. Aber wie weit sollten wir sie genau zurückschieben? Jetzt wird es mathematisch. Das ist mein letztes Mal, versprochen.
Wichtig ist, dass der untere Rand des Daumens beim Scrollen ganz nach unten mit dem unteren Rand des scrollbaren Elements übereinstimmt. Mit anderen Worten: Wenn wir scroller.scrollHeight - scroller.height
Pixel gescrollt haben, soll der Daumen um scroller.height - thumb.height
verschoben werden. Für jedes Pixel des Scrollers soll sich der Schieberegler um einen Bruchteil eines Pixels bewegen:
Das ist unser Skalierungsfaktor. Jetzt müssen wir den Skalierungsfaktor in eine Übersetzung entlang der z-Achse umwandeln, was wir bereits im Artikel mit Parallaxe-Scrollen getan haben. Gemäß dem entsprechenden Abschnitt in der Spezifikation:
Der Skalierungsfaktor ist gleich p ÷ (p − z). Wir können diese Gleichung nach z lösen, um herauszufinden, wie weit wir unseren Daumen entlang der Z‑Achse verschieben müssen. Beachten Sie jedoch, dass wir aufgrund von Unstimmigkeiten mit der Koordinaten-Koordinaten ein zusätzliches -2px
-Element entlang von z übersetzen müssen. Beachten Sie auch, dass die Transformationen eines Elements von rechts nach links angewendet werden. Das bedeutet, dass alle Übersetzungen vor unserer speziellen Matrix nicht invertiert werden, alle Übersetzungen nach der speziellen Matrix jedoch. Lasst uns das codifizieren.
<script>
// ... code from above...
const factor =
(scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
thumb.style.transform = `
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
scale(${1/factor})
translateZ(${1 - 1/factor}px)
translateZ(-2px)
`;
</script>
Wir haben eine Bildlaufleiste. Es ist nur ein DOM-Element, das wir nach Belieben stylen können. Aus Sicht der Barrierefreiheit ist es wichtig, dass der Schieberegler auf „Klicken und Ziehen“ reagiert, da viele Nutzer mit einer solchen Interaktion mit einer Bildlaufleiste vertraut sind. Um diesen Blogpost nicht noch länger zu machen, werde ich die Details zu diesem Teil nicht erläutern. Weitere Informationen dazu finden Sie im Bibliothekscode.
Wie sieht es mit iOS aus?
Ah, mein alter Freund iOS Safari. Wie beim Parallaxen-Scrolling treten hier Probleme auf. Da wir über ein Element scrollen, müssen wir -webkit-overflow-scrolling: touch
angeben. Das führt jedoch zu einer 3D-Ebene und der gesamte Scrolleffekt funktioniert nicht mehr. Wir haben dieses Problem im Paralax-Scroller gelöst, indem wir iOS Safari erkannt und position: sticky
als Problemumgehung verwendet haben. Genau das werden wir hier auch tun. Sehen Sie sich den Artikel zu Parallaxen noch einmal an, um Ihr Gedächtnis aufzufrischen.
Was ist mit der Bildlaufleiste des Browsers?
Bei einigen Systemen müssen wir mit einer permanenten, nativen Bildlaufleiste arbeiten.
Bisher konnte die Bildlaufleiste nicht ausgeblendet werden (außer mit einem nicht standardmäßigen Pseudo-Sellektor).
Um es zu verstecken, müssen wir uns also auf ein paar (mathefreie) Hackerangriffe umsehen. Wir legen unser scrollbares Element mit overflow-x: hidden
in einen Container und machen es breiter als den Container. Die native Bildlaufleiste des Browsers ist jetzt nicht mehr sichtbar.
Fin
Wenn wir alles zusammenfügen, können wir jetzt einen benutzerdefinierten Bildlaufbalken erstellen, der rahmengenau ist – wie der in unserer Nyan-Katze-Demo.
Wenn Sie Nyan Cat nicht sehen, liegt ein Fehler vor, den wir bei der Erstellung dieser Demo gefunden und gemeldet haben. Klicken Sie auf den Daumen, um Nyan Cat zu sehen. Chrome vermeidet unnötige Arbeit, z. B. das Zeichnen oder Animieren von Elementen, die nicht auf dem Bildschirm zu sehen sind. Die schlechte Nachricht ist, dass Chrome aufgrund unserer Matrix-Spielereien denkt, dass das Nyan-Cat-GIF tatsächlich außerhalb des Bildschirms ist. Wir hoffen, dass das Problem bald behoben wird.
Das war es auch schon. Das war eine Menge Arbeit. Danke, dass Sie das gesamte Dokument gelesen haben. Es ist ziemlich schwierig, das zum Laufen zu bringen, und es lohnt sich wahrscheinlich nur selten, es zu versuchen – es sei denn, eine benutzerdefinierte Bildlaufleiste ist ein wesentlicher Bestandteil der Website. Aber gut zu wissen, dass es möglich ist, oder? Die Tatsache, dass es so schwierig ist, eine benutzerdefinierte Bildlaufleiste zu erstellen, zeigt, dass es noch viel zu tun gibt. Aber keine Sorge! In Zukunft wird AnimationWorklet von Houdini viel einfacher machen, wenn man per Scrollen perfekte Frames verlinkt.