Skuteczna paralaksa

Niezależnie od tego, czy Ci się to podoba, czy nie, efekt paralaksy już na stałe zagościł w naszych usługach. Umiejętnie stosowany może dodać głębi i subtelności aplikacji internetowej. Problem polega jednak na tym, że wydajne wdrożenie efektu paralaksy może być trudne. W tym artykule omówimy rozwiązanie, które jest zarówno wydajne, jak i – co równie ważne – działa w różnych przeglądarkach.

Ilustracja z efektem paralaksy.

TL;DR

  • Do tworzenia animacji paralaksy nie używaj zdarzeń przewijania ani background-position.
  • Używaj przekształceń 3D CSS, aby uzyskać dokładniejszy efekt paralaksy.
  • W przypadku przeglądarki Safari na urządzenia mobilne użyj parametru position: sticky, aby efekt paralaksy został rozpropagowany.

Jeśli chcesz skorzystać z gotowego rozwiązania, przejdź do repozytorium GitHub z przykładowymi elementami interfejsu i pobierz plik JS z pomocą dotyczącą efektu paralaksy. Wersję demonstracyjną przewijania z efektem paralaksy możesz zobaczyć w repozytorium GitHub.

Problem parallaxers

Na początek przyjrzyjmy się 2 popularnym sposobom uzyskania efektu paralaksy, a w szczególności temu, dlaczego nie nadają się one do naszych celów.

Nieprawidłowo: korzystanie ze zdarzeń przewijania

Kluczowym wymaganiem dotyczącym efektu paralaksy jest to, że powinien być powiązany ze scrollowaniem. Przy każdej zmianie pozycji przewijania strony powinna się aktualizować pozycja elementu z efektem paralaksy. Choć brzmi to prosto, ważnym mechanizmem nowoczesnych przeglądarek jest ich zdolność do pracy asynchronicznej. W naszym przypadku dotyczy to zdarzeń przewijania. W większości przeglądarek zdarzenia przewijania są dostarczane w miarę możliwości i nie ma gwarancji, że będą dostarczane w każdej klatce animacji przewijania.

Ta ważna informacja mówi nam, dlaczego musimy unikać rozwiązania opartego na JavaScript, które przesuwa elementy na podstawie zdarzeń przewijania: JavaScript nie gwarantuje, że efekt paralaksy będzie zgodny z pozycją przewijania strony. W starszych wersjach Mobile Safari zdarzenia przewijania były faktycznie dostarczane na końcu przewijania, co uniemożliwiało tworzenie efektów przewijania opartych na JavaScript. Nowsze wersje przekazują zdarzenia przewijania podczas animacji, ale podobnie jak Chrome robią to w miarę możliwości. Jeśli wątek główny jest zajęty innymi zadaniami, zdarzenia przewijania nie będą dostarczane od razu, co oznacza, że efekt paralaksy zostanie utracony.

Nieprawidłowo: aktualizowanie background-position

Inną sytuacją, której chcemy uniknąć, jest malowanie na każdej klatce. Wiele rozwiązań próbuje zmienić background-position, aby uzyskać efekt paralaksy, co powoduje, że przeglądarka odmalowuje odpowiednie części strony podczas przewijania, a to może być na tyle kosztowne, że spowoduje znaczne zacinanie się animacji.

Jeśli chcemy spełnić obietnicę efektu paralaksy, potrzebujemy czegoś, co można zastosować jako przyspieszoną właściwość (co obecnie oznacza trzymanie się przekształceń i przezroczystości) i co nie opiera się na zdarzeniach przewijania.

CSS w 3D

Zarówno Scott Kellum, jak i Keith Clark wykonali znaczną pracę w zakresie wykorzystania CSS 3D do uzyskania efektu paralaksy. Stosowana przez nich technika polega na tym, że:

  • Skonfiguruj element zawierający, aby przewijał się z użyciem overflow-y: scroll (i prawdopodobnie overflow-x: hidden).
  • Do tego samego elementu zastosuj wartość perspective i wartość perspective-origin ustawioną na top left lub 0 0.
  • Zastosuj do elementów podrzędnych tego elementu tłumaczenie w osi Z i skaluj je z powrotem, aby uzyskać efekt paralaksy bez wpływu na ich rozmiar na ekranie.

Kod CSS dla tego podejścia wygląda tak:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Zakładamy, że fragment kodu HTML wygląda tak:

<div class="container">
    <div class="parallax-child"></div>
</div>

Dostosowywanie skali do perspektywy

Cofnięcie elementu podrzędnego spowoduje jego zmniejszenie proporcjonalnie do wartości perspektywy. Skalę powiększenia możesz obliczyć za pomocą tego równania: (perspektywa – odległość) / perspektywa. Ponieważ najprawdopodobniej chcemy, aby element paralaksy był widoczny w rozmiarze, w jakim został utworzony, ale jednocześnie miał efekt paralaksy, musimy go powiększyć w ten sposób, zamiast pozostawiać go w oryginalnym rozmiarze.

W przypadku powyższego kodu perspektywa wynosi 1 piksel, a odległość Z elementu parallax-child wynosi -2 piksele. Oznacza to, że element będzie musiał zostać powiększony 3-krotnie, co widać w wartości wstawionej do kodu:scale(3).

W przypadku treści, do których nie zastosowano wartości translateZ, możesz użyć wartości zero. Oznacza to, że skala to (perspektywa – 0) / perspektywa, co daje wartość 1, czyli nie została ona ani zwiększona, ani zmniejszona. Bardzo przydatne.

Jak działa ta metoda

Warto wyjaśnić, dlaczego to działa, ponieważ wkrótce wykorzystamy tę wiedzę. Przewijanie to w zasadzie przekształcenie, dlatego można je przyspieszyć. Polega ono głównie na przesuwaniu warstw za pomocą procesora graficznego. W przypadku typowego przewijania, które nie uwzględnia perspektywy, przewijanie odbywa się w stosunku 1:1 w porównaniu z elementem przewijanym i jego elementami podrzędnymi. Jeśli przewiniesz element w dół o 300px, jego elementy podrzędne zostaną przesunięte w górę o tę samą wartość: 300px.

Jednak zastosowanie wartości perspektywy do elementu przewijanego zaburza ten proces, ponieważ zmienia macierze, które są podstawą transformacji przewijania. Teraz przewinięcie o 300 pikseli może przesunąć elementy podrzędne tylko o 150 pikseli, w zależności od wybranych wartości perspectivetranslateZ. Jeśli element ma wartość 0, będzie przewijany w stosunku 1:1 (tak jak wcześniej), ale element podrzędny przesunięty w kierunku osi Z od punktu początkowego perspektywy będzie przewijany z inną szybkością.translateZ Efekt: ruch paralaksy. Co bardzo ważne, jest to obsługiwane automatycznie w ramach wewnętrznego mechanizmu przewijania przeglądarki, co oznacza, że nie musisz nasłuchiwać zdarzeń scroll ani zmieniać background-position.

Mała niedogodność: Safari na urządzeniach mobilnych

Każdy efekt ma swoje ograniczenia, a w przypadku przekształceń ważne jest zachowanie efektów 3D w elementach podrzędnych. Jeśli w hierarchii między elementem z perspektywą a jego elementami podrzędnymi z efektem paralaksy znajdują się inne elementy, perspektywa 3D jest „spłaszczana”, co oznacza, że efekt zostaje utracony.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

W powyższym kodzie HTML element .parallax-container jest nowy i spowoduje spłaszczenie wartości perspective, co spowoduje utratę efektu paralaksy. Rozwiązanie w większości przypadków jest dość proste: dodajesz transform-style: preserve-3d do elementu, co powoduje propagowanie wszelkich efektów 3D (takich jak wartość perspektywy) zastosowanych wyżej w drzewie.

.parallax-container {
  transform-style: preserve-3d;
}

W przypadku Mobile Safari sytuacja jest jednak nieco bardziej skomplikowana. Zastosowanie overflow-y: scroll do elementu kontenera technicznie działa, ale kosztem możliwości przewijania elementu. Rozwiązaniem jest dodanie elementu -webkit-overflow-scrolling: touch, ale spowoduje to też spłaszczenie elementu perspective, przez co nie uzyskamy efektu paralaksy.

Z punktu widzenia stopniowego ulepszania nie jest to raczej duży problem. Jeśli nie możemy zastosować efektu paralaksy w każdej sytuacji, nasza aplikacja nadal będzie działać, ale warto znaleźć obejście tego problemu.

position: sticky na ratunek!

W tym celu możesz użyć właściwości position: sticky, która umożliwia „przyklejanie” elementów do górnej krawędzi obszaru widocznego lub danego elementu nadrzędnego podczas przewijania. Specyfikacja, jak większość tego typu dokumentów, jest dość obszerna, ale zawiera przydatny szczegół:

Na pierwszy rzut oka może się to wydawać mało istotne, ale kluczowym elementem tego zdania jest odniesienie do sposobu obliczania przyczepności elementu: „przesunięcie jest obliczane w odniesieniu do najbliższego elementu nadrzędnego z polem przewijania”. Inaczej mówiąc, odległość, o którą należy przesunąć przyklejony element (aby wydawał się przyłączony do innego elementu lub obszaru widocznego), jest obliczana przed zastosowaniem innych przekształceń, a nie po ich zastosowaniu. Oznacza to, że podobnie jak w przykładzie z przewijaniem, jeśli przesunięcie zostało obliczone na 300 pikseli, istnieje nowa możliwość użycia perspektywy (lub dowolnej innej transformacji) do manipulowania tą wartością przesunięcia przed zastosowaniem jej do elementów przyklejonych.

Stosując właściwość position: -webkit-sticky do elementu z efektem paralaksy, możemy skutecznie „odwrócić” efekt spłaszczenia właściwości -webkit-overflow-scrolling: touch. Dzięki temu element paralaksy odwołuje się do najbliższego elementu nadrzędnego z polem przewijania, którym w tym przypadku jest .container. Następnie, podobnie jak wcześniej, .parallax-container stosuje wartość perspective, która zmienia obliczone przesunięcie przewijania i tworzy efekt paralaksy.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Przywraca to efekt paralaksy w Mobile Safari, co jest świetną wiadomością.

Ograniczenia dotyczące przyklejonego pozycjonowania

Jest tu jednak pewna różnica: position: sticky zmienia mechanizm paralaksy. Pozycjonowanie przyklejone próbuje przykleić element do kontenera przewijania, a wersja nieprzyklejona tego nie robi. Oznacza to, że efekt paralaksy z przyklejonymi końcami jest odwrotny do efektu bez nich:

  • W przypadku position: sticky im bliżej elementu jest do z=0, tym mniej się on przesuwa.
  • Bez position: sticky im bliżej elementu do z=0, tym bardziej się on przesuwa.

Jeśli to wszystko wydaje Ci się nieco abstrakcyjne, obejrzyj tę prezentację Roberta Flacka, która pokazuje, jak elementy zachowują się inaczej z pozycjonowaniem stałym i bez niego. Aby zobaczyć różnicę, musisz używać Chrome Canary (w momencie pisania tego artykułu jest to wersja 56) lub Safari.

Zrzut ekranu z perspektywą paralaksy

Demonstracja Roberta Flacka pokazująca, jak position: sticky wpływa na przewijanie z efektem paralaksy.

Różne błędy i rozwiązania

Jak w każdej dziedzinie, wciąż są jednak pewne niedoskonałości, które należy wyeliminować:

  • Obsługa przyklejania jest niespójna. Obsługa jest nadal wdrażana w Chrome, Edge w ogóle nie obsługuje tej funkcji, a Firefox ma błędy rysowania, gdy pozycja przyklejona jest połączona z przekształceniami perspektywy. W takich przypadkach warto dodać trochę kodu, aby dodawać tylko position: sticky (wersję z prefiksem -webkit-), gdy jest to potrzebne, czyli tylko w przypadku Mobile Safari.
  • Efekt nie działa w Edge. Przeglądarka Edge próbuje obsługiwać przewijanie na poziomie systemu operacyjnego, co jest zwykle korzystne, ale w tym przypadku uniemożliwia wykrywanie zmian perspektywy podczas przewijania. Aby rozwiązać ten problem, możesz dodać element o stałej pozycji, ponieważ wydaje się, że przełącza on przeglądarkę Edge na metodę przewijania niezależną od systemu operacyjnego i zapewnia uwzględnianie zmian perspektywy.
  • „Treści na stronie właśnie się powiększyły!” Wiele przeglądarek uwzględnia skalę, gdy decyduje o wielkości treści strony, ale niestety Chrome i Safari nie uwzględniają perspektywy. Jeśli na element zastosowano skalę 3x, możesz zobaczyć paski przewijania itp., nawet jeśli po zastosowaniu perspective element ma skalę 1x. Ten problem można obejść, skalując elementy od prawego dolnego rogu (za pomocą transform-origin: bottom right). Działa to, ponieważ spowoduje, że zbyt duże elementy będą powiększać się w „obszarze ujemnym” (zwykle w lewym górnym rogu) obszaru przewijanego. Obszary przewijane nigdy nie pozwalają zobaczyć ani przewinąć treści w obszarze ujemnym.

Podsumowanie

Paralaksa to ciekawy efekt, jeśli jest używana z rozmysłem. Jak widać, można to zaimplementować w sposób wydajny, powiązany z przewijaniem i działający w różnych przeglądarkach. Wymaga to nieco matematycznych wyliczeń i niewielkiej ilości kodu, aby uzyskać pożądany efekt. Dlatego przygotowaliśmy małą bibliotekę pomocniczą i przykładowy kod, które znajdziesz w naszym repozytorium GitHub z przykładowymi elementami interfejsu.

Wypróbuj tę funkcję i daj nam znać, jak Ci poszło.