CSS Deep-Dive – matrix3d() – niestandardowy pasek przewijania idealnie dopasowany do ramki

Niestandardowe paski przewijania są bardzo rzadkie. Główną przyczyną jest to, paski przewijania to jeden z pozostałych elementów internetu, niestylowa (patrzę na Ciebie, selektor daty). Można używać JavaScriptu do tworzenia własnych treści, ale jest to kosztowne i niedrogie. i może działać z opóźnieniem. W tym artykule omówimy za pomocą niekonwencjonalnych macierzy CSS do utworzenia niestandardowego elementu przewijania, który nie wymaga JavaScript podczas przewijania, tylko trochę kodu konfiguracji.

TL;DR

Nie interesują Cię drobiazgi? Wystarczy spojrzeć na Prezentacja z kotami Nyan i dołączyć do biblioteki? Kod demonstracyjny znajdziesz w repozytorium GitHub.

LAM;WRA (długie i matematyczne – można przeczytać mimo to)

Jakiś czas temu zbudowaliśmy mechanizm przewijania paralaksy (Czy znasz ten artykuł? To naprawdę dobre, warte Twojego czasu). Odpychając elementy za pomocą CSS 3D po przekształceniu, elementy przesuwały się wolniej niż rzeczywista szybkość przewijania.

Podsumowanie

Zacznijmy od podsumowania działania funkcji przewijania paralaksy.

Jak pokazano na animacji, efekt paralaksy udało się uzyskać, przesuwając elementy. „do tyłu” w przestrzeni 3D, wzdłuż osi Z. Przewijanie dokumentu przesunięcia wzdłuż osi Y. Jeśli więc przewiniesz w dół o, powiedzmy 100 pikseli, zostanie przesunięty w górę o 100 pikseli. Dotyczy to wszystkich elementów, nawet tych, które są „bardziej oddalone”. Ale ponieważ są one w większej odległości aparat, obserwowany ruch na ekranie będzie mniejszy niż 100 pikseli, odpowiedni efekt paralaksy.

Oczywiście przesunięcie elementu w przestrzeń kosmiczna też zmniejszy jego rozmiar, Korygowany jest przez ponowne skalowanie elementu. Dokładnie obliczyliśmy gdy tworzyliśmy paralaksa przewijana, więc nie będę powtarzać wszystkich szczegółów.

Krok 0. Co chcesz zrobić?

Paski przewijania. To właśnie zamierzamy stworzyć. Ale czy kiedykolwiek zastanawialiście się, o tym, co robią? Ja nie. Paski przewijania wskazują na: jaka część dostępnych treści jest obecnie widoczna i jaki jest postęp. jak czytelnik. Jeśli przewiniesz w dół, pasek przewijania do wskazuje, że idzie Ci do końca. Jeśli cała zawartość pasuje w widocznym obszarze, a pasek przewijania jest zwykle ukryty. Jeśli treść jest 2 razy większa niż widoczny obszar, wypełnia on 1⁄2 wysokości widocznego obszaru. Treść warta 3 razy więcej widoczny obszar skaluje pasek przewijania do 1⁄3 widocznego obszaru itd. Widać wzorzec. Zamiast przewijać, możesz też kliknąć i przeciągnąć pasek przewijania, aby poruszać się po nim. działanie strony. To zaskakujące zachowania dla tak niepozornej firmy, taki element. Zagrajmy po jednej bitwie.

Krok 1. Odwrotność

W CSS 3D możemy sprawić, że elementy będą przesuwały się wolniej niż przewijanie zgodnie z opisem w artykule z przewijaniem z efektem paralaksy. Czy możemy też odwrócić kolejność wskazówkę? Okazuje się, że możemy to zrobić. Niestandardowy pasek przewijania. Aby zrozumieć, jak to działa, musimy omówić podstawy CSS 3D.

Aby uzyskać dowolną perspektywę w sensie matematycznym, trzeba prawdopodobnie wykorzystasz jednorodne współrzędne. Nie chcę mówić szczegółowo, czym są i dlaczego działają, ale możesz pomyśleć o tym, je jak współrzędne 3D z dodatkową, czwartą współrzędną o nazwie W. Ten powinna wynosić 1, chyba że chcesz uzyskać zniekształcenie perspektywy. Śr nie musimy zaprzątać sobie głowy szczegółami w, ponieważ nie będziemy używać żadnego inną niż 1. Dlatego wszystkie punkty pochodzą od teraz na 4-wymiarowe wektory [x, y, z, w=1], a w związku z tym macierze muszą 4 x 4.

Jedną z sytuacji, w której można zobaczyć, że usługa porównywania cen używa jednorodnych współrzędnych w kolumnie definiowania własnych macierzy 4x4 we właściwości przekształcenia za pomocą funkcji matrix3d(). Funkcja matrix3d przyjmuje 16 argumentów (ponieważ tablica to 4 x 4), określając jedną kolumnę po drugiej. Możemy więc użyć tej funkcji do ręcznie określać obroty, tłumaczenia itd. Pozwala nam to jednak ma bałagan z koordynatorem.

Zanim zaczniemy używać elementu matrix3d(), potrzebny jest kontekst 3D. w kontekście 3D nie doszłoby do zniekształcenia perspektywy. jednorodne współrzędne. Aby utworzyć kontekst 3D, potrzebujemy kontenera z perspective i kilka znajdujących się w nim elementów, które możemy przekształcić w nowoczesnym utworzono przestrzeń 3D. Dla: przykład:

Fragment kodu CSS zniekształcający element div za pomocą atrybutu CSS
    i perspektywy.

Elementy wewnątrz kontenera perspektywy są przetwarzane przez silnik CSS w następujący sposób:

  • Przekształć każdy róg (wierzchołek) elementu w jednorodne współrzędne [x,y,z,w] względem kontenera perspektywy.
  • Zastosuj wszystkie przekształcenia elementu jako macierze od prawej do lewej.
  • Jeśli perspektywę można przewijać, zastosuj macierz przewijania.
  • Zastosuj macierz perspektyw.

Macierz przewijania to przesunięcie wzdłuż osi Y. Jeśli przewiniemy w dół o 400 pikseli, wszystkie elementy należy przesunąć w górę o 400 pikseli. Macierz perspektyw to macierz, która „przyciąga” punkty bliżej punktu znikania w trójwymiarze, w przestrzeni kosmicznej. Efekt jest w obu przypadkach mniejszy. są oddalone od siebie i sprawiają, że podczas tłumaczenia „przesuwają się wolniej”. Jeśli element zostanie przesunięty wstecz, przesunięcie o 400 pikseli spowoduje, że element przesunięcie o 300 pikseli po ekranie.

Aby poznać wszystkie szczegóły, przeczytaj specyfikacja usługi porównywania cen ale na potrzeby tego artykułu uprościłem model algorytmem opisanym powyżej.

Nasze pudełko znajduje się wewnątrz kontenera perspektywy z wartością p dla perspective i załóżmy, że kontener można przewijać n pikseli.

Macierz perspektywy pomnożona przez macierz przewijania razy macierz przekształceń elementu
  równa się macierz tożsamości 4 na 4, przy czym w 4 rzędzie minus jeden nad p
  trzecia kolumna razy cztery na cztery matryca tożsamości z minusem n w drugiej
  czwarta kolumna razy macierz przekształceń elementu.

Pierwsza macierz to macierz perspektyw, a druga to przewijanie. lub macierz. Podsumujmy: zadaniem macierzy przewijania jest przesuwanie w górę elementu, przewijają w dół, stąd znak minus.

Na pasku przewijania chcemy jednak widzieć przeciwieństwem – chcemy, by element przesuń w dół, gdy przewijamy w dół. Oto miejsca, w których możemy użyć sztuczki: Odwrócenie współrzędnej W rogów prostokąta. Jeśli współrzędna W to -1, wszystkie tłumaczenia będą działały w odwrotnym kierunku. Jak to? Mechanizm CSS konwertuje rogi pola na jednorodne współrzędne i ustawia w na 1. matrix3d() – czas zabłysnąć!

.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 więc mechanizm usług porównywania cen przekształci każdy róg w wektor o postaci [x,y,z,1], macierz przekonwertować go na: [x,y,z,-1].

Macierz tożsamości 4 na 4 z minusem jeden nad p w 4. rzędzie
  trzecia kolumna razy cztery na cztery matryca tożsamości z minusem n w drugiej
  czwarta kolumna w wierszu razy cztery na cztery macierz tożsamości z minusem 1 w argumencie
  czwarty wiersz, czwarta kolumna razy cztery wektory wymiarowe x, y, z, 1 równa się cztery
  przez cztery macierz tożsamości przy minus jeden nad p w trzeciej kolumnie w czwartym rzędzie
  minus n w 4 wierszu i minus 1 w czwartym wierszu
  czwarta kolumna równa się czterema wymiarom wektora x, y + n, z, minus z
  p minus 1.

Jestem przedstawiony jako krok pośredni, który pokazuje efekt przekształcenia elementu. lub macierz. Jeśli nie czujesz się dobrze z matematyką matrycową, nic nie szkodzi. Eureka Moment jest taki, że w ostatnim wierszu dodajemy przesunięcie przewijania n do wartości y zamiast jej odejmowania. Element zostanie przesunięty w dół. po przewinięciu w dół.

Jeśli jednak umieścimy tę macierz w naszym przykład, element nie będzie wyświetlany. Dzieje się tak, ponieważ specyfikacja CSS wymaga, wierzchołek z w < Wartość 0 blokuje renderowanie elementu. A ponieważ współrzędna to obecnie 0, a p to 1, W to -1.

Na szczęście możemy wybrać wartość z! Aby mieć pewność, że kończymy w=1, musimy , aby ustawić Z = –2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

Oto nasz box powraca!

Krok 2. Daj z siebie wszystko

Nasze pudełko jest dostępne i wygląda tak samo jak bez innych przekształcenia. Obecnie kontenera perspektywy nie można przewijać, ale wiemy, że nasz element pójdzie w innym kierunku, gdy przewinięto. Może zaczniemy przewijać kontener? Wystarczy 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ń okno! Czerwone pole przesuwa się w dół.

Krok 3. Dobierz odpowiedni rozmiar

Widać element, który przesuwa się w dół, gdy strona przewija się w dół. To jest to trudne że brakuje im drogi. Teraz musimy nadać jej styl, tak by wyglądał jak pasek przewijania, uczynić go bardziej interaktywnym.

Pasek przewijania zwykle składa się z „kciuka” i „ścieżki”, ale nie jest zawsze widoczne. Wysokość kciuka jest wprost proporcjonalna do wysokości jest widoczna.

<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 część treści, która jest widoczne. Odstęp między osłonami na kciuki w pionie powinien być równy proporcje widocznej treści:

styl kropki
  nad elementem przewijania kropka przewijanie wysokość jeśli i tylko jeśli wysokość punktu
  równa się wysokość elementu przewijania pomnożona przez wysokość elementu przewijania nad kropką przewijania
  wysokość.
<script>
    // …
    thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
    // Accommodate for native scrollbars
    thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

Rozmiar kciuka to wygląda nieźle, ale porusza się za szybko. W tym miejscu opanujemy technikę funkcja przewijania paralaksy. Jeśli przesuniemy element dalej do tyłu, będzie on przesuwał się wolniej, przewijanie. Możemy go skorygować, skalując go w górę. Ale jak bardzo odwrócić? Czas na matematykę! To ostatni raz, lub obiecywanie.

Ważne jest, aby kciukiem znajdował się na dole wyrównaj z dolną krawędzią elementu możliwego do przewijania po przewinięciu do końca w dół. Innymi słowy: jeśli przewinęliśmy stronę scroller.scrollHeight - scroller.height pikseli, chcemy mieć kciuk tłumaczenie: scroller.height - thumb.height. Na każdy piksel przewijania chcemy, by kciuk przemieścił ułamek piksela:

Współczynnik równa się wysokości punktu przewijania minus wysokość punktu przewijania nad elementem przewijania
  wysokość przewijania kropką minus wysokość punktu przewijania.

To nasz współczynnik skalowania. Teraz trzeba przekonwertować współczynnik skalowania na przesunięcie wzdłuż osi Z, co zrobiliśmy już przy przewijaniu paralaksy. . Według odpowiedniej sekcji specyfikacji: Współczynnik skalowania jest równy p/(p – z). Możemy rozwiązać równanie Z do określić, jak bardzo kciuk należy przesunąć wzdłuż osi Z. Ale zachowaj pamiętaj, że ze względu na koordynację działań musimy przetłumaczyć dodatkowe -2px wzdłuż wartości z. Pamiętaj też, że stosowane są przekształcenia elementu. od prawej do lewej, co oznacza, że tłumaczenia sprzed naszej specjalnej macierzy zostaną odwrócone, jednak wszystkie tłumaczenia po naszej specjalnej macierzy Zaczynajmy Skoduj 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 pasku przewijania. Jest to po prostu element DOM, który możemy dowolnie zmieniać. Jedna rzecz, w przypadku ułatwień dostępu ważne jest, aby kciuk reagował kliknij i przeciągnij, bo wielu użytkowników korzysta z paska przewijania w ten sposób. Aby nie wydłużać tego posta, nie będę wyjaśniać, aby dowiedzieć się więcej na ten temat. Zwróć uwagę na: kod biblioteki .

A co z iOS?

Ach, mój stary przyjaciel Safari na iOS. Podobnie jak przy przewijaniu z paralaksą, natknęliśmy się na ten problem. Ponieważ przewijamy element, musimy określić -webkit-overflow-scrolling: touch, ale powoduje to wygładzanie 3D i efekt przewijania przestanie działać. Rozwiązaliśmy ten problem w narzędziu do przewijania paralaksy wykrywając przeglądarkę Safari w iOS i stosując protokół position: sticky jako obejście problemu; tutaj zrobimy dokładnie to samo. Zwróć uwagę na: artykuł paralaksujący aby odświeżyć wspomnienie.

A co z paskiem przewijania w przeglądarce?

W przypadku niektórych systemów musimy korzystać ze stałego, natywnego paska przewijania. Dawniej paska przewijania nie można było ukryć (z wyjątkiem niestandardowy pseudoselektor). Aby ją ukryć, musimy uciekać się do jakiegoś ataku hakerów. Skupiamy się na przewijany element w kontenerze z parametrem overflow-x: hidden i ustaw tag przewijany element jest szerszy niż kontener. Natywny pasek przewijania w przeglądarce to jest teraz niewidoczny.

Fin

Po połączeniu wszystkich tych elementów możemy utworzyć idealną ramkę paska przewijania, takiego jak Prezentacja Nyan Cat.

Jeśli nie widzisz Nyan cat, wykryty i zgłoszony błąd podczas tworzenia wersji demonstracyjnej (kliknij kciuk, aby pojawił się kot Nyan). Chrome naprawdę świetnie radzi sobie z unikaniem niepotrzebnej pracy np. malowania lub animowania rzeczy, które nie są widoczne na ekranie. Zła wiadomość jest taka, sprawiają, że Chrome uważa, że ten GIF z kotem Nyan nie pojawia się na ekranie. Mamy nadzieję, że wkrótce problem zostanie rozwiązany.

I o to chodzi. Wymagało to dużo pracy. Dziękuję za przeczytanie całego To jest niektóre jest to dość skomplikowane i niezwykle jest warte wysiłku, z wyjątkiem sytuacji, gdy niestandardowy pasek przewijania jest istotnym elementem obsługi. Ale dobrze wiedzieć, że to możliwe, prawda? Fakt, że jest to trudne do zrobienia, niestandardowy pasek przewijania pokazuje, że po stronie CSS jest jeszcze coś do zrobienia. Ale nie bójcie się! W przyszłości Houdini AnimationWorklet zrobi czy tworzenie idealnych ramek czy przewijanie.