Niestandardowe suwaki są niezwykle rzadkie, głównie dlatego, że są jednymi z nielicznych elementów w internecie, których nie da się sformatować (patrz: selektor daty). Możesz użyć JavaScriptu, aby utworzyć własną, ale jest to drogie, ma niską wierność i może działać wolno. W tym artykule użyjemy niestandardowych macierzy CSS, aby utworzyć niestandardowy scroller, który nie wymaga żadnego kodu JavaScript podczas przewijania, tylko trochę kodu konfiguracyjnego.
TL;DR
Nie interesują Cię drobiazgi? Chcesz po prostu obejrzeć prezentację o kotach Nyan i pobrać bibliotekę? Kod wersji demonstracyjnej znajdziesz w repozytorium GitHub.
LAM;WRA (długi i matematyczny; i tak przeczytasz)
Jakiś czas temu stworzyliśmy scroller z efektem paralaksy (czy czytałeś/czytałaś ten artykuł? To naprawdę świetne rozwiązanie, które warto wypróbować. Odpychanie elementów za pomocą przekształceń CSS 3D sprawia, że przesuwają się one wolniej niż nasza rzeczywista szybkość przewijania.
Podsumowanie
Zacznijmy od podsumowania działania funkcji przewijania paralaksy.
Jak widać na animacji, efekt paralaksy uzyskaliśmy, przesuwając elementy „do tyłu” w przestrzeni 3D wzdłuż osi Z. Przewijanie dokumentu to w istocie przesunięcie wzdłuż osi Y. Jeśli przewiniesz w dół o 100 pikseli, każdy element zostanie przesunięty w górę o 100 pikseli. Dotyczy to wszystkich elementów, nawet tych, które są „dalej”. Ponieważ są one dalej od kamery, ich obserwowane na ekranie ruch będzie mniejszy niż 100 pikseli, co da pożądany efekt paralaksy.
Oczywiście przesunięcie elementu w przestrzeni powoduje, że wydaje się on mniejszy, co korygujemy przez zwiększenie jego rozmiaru. Dokładne obliczenia zostały określone podczas tworzenia funkcji przewijania paralaksy, więc nie będę powtarzać wszystkich szczegółów.
Krok 0. Co chcemy zrobić?
paski przewijania; To właśnie zamierzamy stworzyć. Czy zastanawiałeś/się kiedyś, co one robią? Nie, nie zrobiłem tego. Paski przewijania wskazują, ile z dostępnych treści jest obecnie widocznych oraz jak duży postęp w czytaniu udało Ci się osiągnąć. Gdy przewijasz w dół, pasek przewijania również się przesuwa, aby pokazać, że zbliżasz się do końca. Jeśli wszystkie treści mieszczą się w widocznym obszarze, suwak jest zwykle ukryty. Jeśli wysokość treści jest 2 razy większa od wysokości widocznego obszaru, suwak wypełnia połowę wysokości widocznego obszaru. Treści warte 3 razy wysokość widocznego obszaru skalują pasek przewijania do 1⁄3 widocznego obszaru itp. Widać tutaj wzorzec. Zamiast przewijać, możesz też kliknąć i przeciągnąć suwak, aby szybciej poruszać się po stronie. To zaskakujące zachowanie dla tak niepozornego elementu. Walczmy w jednym miejscu.
Krok 1. Wsteczny
Możemy sprawić, aby elementy poruszały się wolniej niż prędkość przewijania za pomocą transformacji 3D w CSS, jak opisano w artykule o przewijaniu paralaksy. Czy możemy też odwrócić kierunek? Okazuje się, że możemy to zrobić – w ten sposób stworzyliśmy idealny niestandardowy pasek przewijania. Aby zrozumieć, jak to działa, musimy najpierw omówić kilka podstawowych zagadnień dotyczących CSS 3D.
Aby uzyskać dowolny rodzaj perspektywy w ujęciu matematycznym, prawdopodobnie użyjesz współrzędnych jednorodnych. Nie będę wchodzić w szczegóły, czym są i dlaczego działają, ale możesz je sobie wyobrazić jako współrzędne 3D z dodatkową, czwartą współrzędną o nazwie w. Ta współrzędna powinna mieć wartość 1, chyba że chcesz uzyskać zniekształcenie perspektywy. Nie musisz się martwić szczegółami parametru w, ponieważ nie użyjemy żadnej wartości innej niż 1. Dlatego od teraz wszystkie punkty są 4-wymiarowymi wektorami [x, y, z, w=1], a więc macierze też muszą mieć wymiary 4 × 4.
Jednym z przypadków, w którym widać, że CSS używa współrzędnych niejednorodnych, jest zdefiniowanie własnych macierzy 4 x 4 w właściwości transform za pomocą funkcji matrix3d()
. Funkcja matrix3d
przyjmuje 16 argumentów (ponieważ macierz to 4 x 4), określając jedną kolumnę po drugiej. Dzięki tej funkcji możemy ręcznie określać obroty, przesunięcia itp., ale pozwala ona też manipulować współrzędną w.
Zanim użyjemy funkcji matrix3d()
, potrzebujemy kontekstu 3D, ponieważ bez niego nie byłoby żadnych zniekształceń perspektyw i nie potrzebowałyby one jednakowych współrzędnych. Aby utworzyć kontekst 3D, potrzebujemy kontenera z perspective
i elementami, które możemy przekształcić w nowo utworzonej przestrzeni 3D. Na przykład:
Elementy wewnątrz kontenera perspektywy są przetwarzane przez wyszukiwarkę CSS w ten sposób:
- Przekształcanie każdego narożnika (wierzchołka) elementu w współrzędne jednorodne
[x,y,z,w]
względem kontenera perspektywy. - Zastosuj wszystkie przekształcenia elementu jako macierze od prawej do lewej.
- Jeśli element perspektywy można przewijać, zastosuj matrycę przewijania.
- Zastosuj macierz perspektywy.
Macierz przewijania to przesunięcie wzdłuż osi y. Jeśli przewiniesz się w dół o 400 pikseli, wszystkie elementy trzeba przesunąć w górę o 400 pikseli. Macierz perspektywa to macierz, która „przyciąga” punkty bliżej punktu znikającego w dalszym ciągu w przestrzeni 3D. Dzięki temu obiekty są mniejsze, gdy znajdują się dalej, a także „poruszają się wolniej”, gdy są przenoszone. Jeśli więc element jest przesunięty do tyłu, przesunięcie o 400 pikseli spowoduje przesunięcie elementu tylko o 300 pikseli na ekranie.
Jeśli chcesz poznać wszystkie szczegóły, zapoznaj się ze specyfikacją modelu renderowania z transformacją CSS, ale na potrzeby tego artykułu uprościliśmy powyższy algorytm.
Nasze pole znajduje się wewnątrz kontenera perspektywy z wartością p dla atrybutu perspective
. Załóżmy, że kontener można przewijać o n pikseli.
Pierwsza to macierz perspektyw, a druga – macierz przewijania. Podsumujmy: zadaniem macierzy przewijania jest przesuwanie elementu w górę, gdy przewijamy w dół, stąd znak minus.
W przypadku suwaka chcemy jednak uzyskać efekt odwrotny – chcemy, aby element schodził, gdy przewijamy w dół. Oto nasz trik:
odwrócenie współrzędnej W rogów prostokąta. Jeśli współrzędna w ma wartość -1, wszystkie przesunięcia będą miały odwrotny kierunek. Jak to zrobić? Mechanizm CSS konwertuje rogi pola na jednorodne współrzędne i ustawia w polu wartość 1. Nadszedł czas, aby matrix3d()
zabłysła.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
Ta tablica robi nic innego niż negowanie w. Gdy silnik CSS przekształci każdy wierzchołek w wektory o formie [x,y,z,1]
, przekształci je w [x,y,z,-1]
.
Podałem pośredni krok, aby pokazać efekt transformacji elementu. matrix. Jeśli nie czujesz się pewnie w matematyce macierzy, to nic nie szkodzi. W ostatniej linii zamiast odejmowania do współrzędnej y dodajemy przesunięcie osi zwijania n. Element zostanie przetłumaczony w dół, jeśli przewiniesz w dół.
Jeśli jednak umieścimy tę tablicę w przykładzie, element nie będzie się wyświetlać. Wynika to z tego, że specyfikacja CSS wymaga, aby każdy wierzchołek z w < 0 blokował renderowanie elementu. Ponieważ współrzędna z jest obecnie równa 0, a p równa się 1, w będzie równe –1.
Na szczęście możemy wybrać wartość z. Aby mieć pewność, że w=1, musimy ustawić z = -2.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
I oto nasz box wrócił!
Krok 2. Porusz obiekt
Teraz nasza skrzynka jest widoczna i wygląda tak samo jak bez żadnych przekształceń. Obecnie kontener perspektywiczny nie jest przewijany, więc nie możemy go zobaczyć, ale wiemy, że podczas przewijania element będzie się przesuwał w drugim kierunku. Może zaczniemy przewijać kontener? Możemy dodać element odstępu, który zajmuje miejsce:
<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>
A teraz przewiń pole. Czerwone pole przesuwa się w dół.
Krok 3. Określ rozmiar
Mamy element, który przesuwa się w dół, gdy przewijasz stronę w dół. To naprawdę trudna część. Teraz musimy nadać mu styl, aby wyglądał jak suwak i uczynić go nieco bardziej interaktywnym.
Suwak składa się zwykle z elementu „suwaka” i „ścieżki”, przy czym ścieżka nie jest zawsze widoczna. Wysokość miniatury jest wprost proporcjonalna do tego, ile treści jest widocznych.
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
to wysokość elementu, który można przewijać, a scroller.scrollHeight
to łączna wysokość treści, które można przewijać.
scrollerHeight/scroller.scrollHeight
to ułamek treści, który jest widoczny. Stosunek pionowej przestrzeni, którą pokrywa miniatura, powinien być równy stosunkowi widocznych treści:
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
Rozmiar miniatury wygląda dobrze, ale przesuwa się zbyt szybko. W tym miejscu możemy chwycić technikę z przewijania z paralaksą. Jeśli przesuniemy element dalej w tył, będzie się on wolniej przesuwał podczas przewijania. Możemy go skorygować, skalując go w górę. Ale o ile dokładnie powinniśmy przesunąć termin? Zgadliście – czas na odrobinę matematyki. To ostatni raz, obiecuję.
Najważniejsze jest to, aby dolna krawędź miniatury była wyrównana z dolną krawędzią elementu, który można przewijać, gdy jest on całkowicie przewinięty w dół. Inaczej mówiąc: jeśli przewinęliśmy scroller.scrollHeight - scroller.height
pikseli, chcemy, aby palec przesunął się o scroller.height - thumb.height
. W przypadku każdego piksela kółka przewijania chcemy, aby kciuk przesuwał się o ułamek piksela:
To jest nasz współczynnik skalowania. Teraz musimy przekształcić współczynnik skalowania w translację wzdłuż osi z, co już zrobiliśmy w artykule o przewijaniu paralaksy. Zgodnie z odpowiednią sekcją w specyfikacji: współczynnik skalowania jest równy p/(p – z). Możemy rozwiązać to równanie w przypadku z, aby dowiedzieć się, o ile musimy przesunąć kciuk wzdłuż osi z. Trzeba jednak pamiętać, że ze względu na koordynację działań musimy
przełożyć dodatkowy element -2px
razem z z. Pamiętaj też, że transformacje elementu są stosowane od prawej do lewej, co oznacza, że wszystkie translacje przed naszą specjalną macierzą nie będą odwrócone, ale wszystkie translacje po naszej specjalnej matrycy będą odwrócone. Zakodujmy to.
<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>
Mamy pasek przewijania. Jest to po prostu element DOM, który możemy dowolnie zmieniać. Jeśli chodzi o ułatwienia dostępu, ważne jest, aby kciuk reagował na kliknięcie i przeciągnięcie, ponieważ wielu użytkowników jest przyzwyczajonych do korzystania z paska przewijania w ten sposób. Aby nie wydłużać tego posta, nie będę omawiać tej kwestii. Aby dowiedzieć się, jak to zrobić, zapoznaj się ze z kodem biblioteki.
A co z iOS?
Ach, mój stary przyjaciel Safari na iOS. Podobnie jak w przypadku przewijania z efektem paralaksy, tutaj też mamy problem. Przewijamy element, więc musimy określić właściwość -webkit-overflow-scrolling: touch
. Powoduje to wygładzanie 3D i cały efekt przewijania. Rozwiązaliśmy ten problem w przypadku przewijania paralaksy, wykrywając iOS Safari i korzystając z funkcji position: sticky
jako rozwiązania tymczasowego. Zrobimy dokładnie to samo w tym przypadku. Przeczytaj ten artykuł na temat paralaksy, aby odświeżyć swoją pamięć.
A co z paskiem przewijania w przeglądarce?
W niektórych systemach będziemy musieli korzystać z trwałego, natywnego paska przewijania.
Do tej pory paska przewijania nie można było ukryć (z wyjątkiem niestandardowego pseudoselektora).
Aby go ukryć, musimy uciec się do pewnych (niematematycznych) sztuczek. Element przewijania umieszczamy w kontenerze z wartością overflow-x: hidden
i robimy go szerszym niż kontener. Natywna suwak przeglądarki jest teraz niewidoczny.
Fin
Po połączeniu wszystkich elementów możemy teraz stworzyć pasek przewijania, który idealnie pasuje do ramki – tak jak w naszym przykładzie z kota Nyan.
Jeśli nie widzisz Nyan cat, oznacza to, że podczas tworzenia tej wersji demonstracyjnej wystąpił problem, który został przez nas znaleziony i zgłoszony (kliknij kciuk, aby wyświetlić Nyan cat). Chrome bardzo dobrze unika niepotrzebnej pracy, takiej jak rysowanie lub animowanie elementów, które znajdują się poza ekranem. Złe wieści są takie, że nasze sztuczki z Matriksem sprawiają, że Chrome myśli, że obrazek z kotem Nyan jest poza ekranem. Mamy nadzieję, że wkrótce uda się to naprawić.
I o to chodzi. To było bardzo dużo pracy. Gratuluję przeczytania całego tekstu. Jest to prawdziwa sztuczka, aby to zadziałało, i prawdopodobnie rzadko jest tego warte, chyba że dostosowany suwak jest istotną częścią interfejsu. Dobrze wiedzieć, że to możliwe, prawda? Trudno uwierzyć, że utworzenie niestandardowego paska przewijania jest trudne i widać, że jeszcze jest sporo do zrobienia. Nie martw się. W przyszłości narzędzie AnimationWorklet na kanale Houdini znacznie ułatwi takie działania jak ten z przewijaniem, które idealnie nadają się do zastosowania w klatce.