Skuteczna paralaksa

Robert Flack
Robert Flack

Czy to się kocha, czy nienawidzi, paralaksa pozostanie w naszej mocy. Stosowane z rozwagą mogą nadać aplikacji internetowej głębię i subtelność. Problemem jest jednak to, że skuteczne wdrożenie paralaksy może być trudne. W tym artykule omówimy rozwiązanie, które jest skuteczne i działa też w różnych przeglądarkach.

Ilustracja z paralaksą.

TL;DR

  • Nie używaj zdarzeń przewijania ani parametru background-position do tworzenia animacji paralaksy.
  • Aby uzyskać dokładniejszy efekt paralaksy, użyj przekształceń CSS 3D.
  • W przypadku przeglądarki mobilnej Safari użyj parametru position: sticky, aby mieć pewność, że efekt paralaksy zostanie rozpowszechniony.

Jeżeli szukasz tego rozwiązania, przejdź do repozytorium GitHub z przykładowymi elementami interfejsu i pobierz pomocniczy plik JS Paralaks. Prezentację na żywo mechanizmu przewijania paralaksy znajdziesz w repozytorium GitHub.

Paralaksery do zadań

Na początek spójrzmy na 2 typowe sposoby uzyskiwania efektu paralaksy, a zwłaszcza dlaczego nie nadają się one do naszych celów.

Błąd: używasz zdarzeń przewijania

Najważniejszym wymogiem stosowania paralaksy jest to, aby była ona połączona z przewijaniem. W przypadku każdej zmiany pozycji przewinięcia strony należy zmienić pozycję elementu paralaksowego. Choć brzmi to prosto, ważnym mechanizmem nowoczesnych przeglądarek jest ich zdolność do asynchronicznego działania. Dotyczy to szczególnie przewijania zdarzeń. W większości przeglądarek zdarzenia przewijania są generowane w ramach możliwie najlepszej obsługi i nie można zagwarantować ich wyświetlenia w każdej klatce animacji.

Ta ważna informacja mówi nam, dlaczego musimy unikać rozwiązania opartego na języku JavaScript, które przenosi elementy na podstawie zdarzeń przewijania: JavaScript nie gwarantuje, że paralaksowanie będzie na bieżąco z pozycją przewijania strony. W starszych wersjach Mobile Safari zdarzenia przewijania były w rzeczywistości dostarczane na końcu przewijania, co uniemożliwiało uzyskanie efektu przewijania w języku JavaScript. W nowszych wersjach mają one zdarzenia przewijania w czasie animacji, ale podobnie jak w Chrome, „w miarę możliwości”. Jeśli wątek główny jest zajęty innymi zadaniami, zdarzenia przewijania nie są generowane od razu, co oznacza, że stracisz efekt paralaksy.

Błąd: aktualizuję aplikację background-position

Inną sytuacją, której chcemy uniknąć, jest malowanie każdej klatki. W wielu rozwiązaniach próbuje się zmienić element background-position w celu uzyskania wyglądu paralaksy, co powoduje, że przeglądarka ponownie maluje problematyczne części strony podczas przewijania. Może to być tak kosztowne, że znacznie zaburza animację.

Jeśli zależy nam na ruchu paralaksy, potrzebujemy właściwości, które można stosować jako właściwości z przyspieszeniem (co obecnie oznacza trzymanie się przekształceń i przezroczystości), które nie bazują na zdarzeniach przewijania.

CSS w 3D

Zarówno Scott Kellum, jak i Keith Clark przeprowadzili wiele badań w zakresie wykorzystania CSS 3D do uzyskania ruchu paralaksy. Stosowana przez nich technika wygląda tak:

  • Skonfiguruj element zawierający, aby przewijać za pomocą overflow-y: scroll (i prawdopodobnie overflow-x: hidden).
  • Do tego samego elementu zastosuj wartość perspective, a perspective-origin ustawiony na top left, czyli 0 0.
  • Do elementów podrzędnych tego elementu zastosuj tłumaczenie w orientacji Z i przeskaluj je z powrotem, aby uzyskać ruch paralaksy bez wpływu na ich rozmiar na ekranie.

Kod CSS w tym podejściu 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

Wypchnięcie elementu podrzędnego z powrotem spowoduje, że zmniejszy się on proporcjonalnie do wartości z perspektywy. Aby obliczyć, o ile trzeba go przeskalować w górę, użyj tego równania: (perspektywa – odległość) / perspektywa. Najprawdopodobniej chcemy, aby element paralaksy miał postać paralaksy, ale pojawiał się w rozmiarze, który napisaliśmy, więc trzeba go odpowiednio przeskalować, a nie pozostawić bez zmian.

W przypadku powyższego kodu perspektywa wynosi 1px, a odległość na osi parallax-child to -2 piks. Oznacza to, że element należy skalować w górę o 3x. Jak widać, wartość wpisana w kodzie: scale(3).

W przypadku treści, do których nie zastosowano wartości translateZ, możesz wstawić wartość 0. Oznacza to, że skala ma postać (perspektywa - 0) /perspektywy, która przyjmuje wartość 1, co oznacza, że nie została skalowana ani w górę, ani w dół. Naprawdę przydatny.

Jak to działa

Potrzebujemy jasnego wyjaśnienia, dlaczego to działa, ponieważ za chwilę wykorzystamy tę wiedzę. Przewijanie to w praktyce przekształcenie, dlatego można je przyspieszyć. Polega ono głównie na przesuwaniu warstw za pomocą GPU. W typowym przewijaniu, które nie ma pojęcia o perspektywie, przewijanie odbywa się 1:1 podczas porównywania elementu przewijanego i jego elementów podrzędnych. Jeśli przewiniesz element w dół o 300px, jego elementy podrzędne zostaną przekształcone w górę o tę samą wartość: 300px.

Jednak zastosowanie wartości z perspektywy do przewijanego elementu sprawia, że ten proces jest niekorzystny – zmienia macierze leżące u podstaw przekształcenia przewijania. Teraz przewinięcie o długości 300 pikseli może przesunąć elementy podrzędne o 150 pikseli w zależności od wybranych wartości perspective i translateZ. Jeśli element ma wartość translateZ równą 0, przewija się on z szybkością 1:1 (tak jak wcześniej), ale element podrzędny przesunięty w kierunku Z poza punkt początkowy perspektywy będzie przewijany z inną szybkością. Wynik netto: ruch paralaksy. Co ważne, ta funkcja jest traktowana automatycznie jako część wewnętrznego systemu przewijania w przeglądarce, co oznacza, że nie musisz nasłuchiwać zdarzeń scroll ani zmieniać elementu background-position.

Muszka w kształcie mazi: Mobile Safari

Każdy efekt ma pewne ograniczenia. Jednym z ważniejszych elementów przekształceń jest zachowanie efektów 3D w elementach podrzędnych. Jeśli w hierarchii między elementem z perspektywą a paralaksowymi elementami podrzędnymi występują elementy, perspektywa 3D jest „spłaszczona”, co oznacza, że efekt jest tracony.

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

W powyższym kodzie HTML obiekt .parallax-container jest nowy, co spowoduje spłaszczenie wartości perspective oraz utratę efektu paralaksy. Rozwiązanie, w większości przypadków, jest dość proste: dodajesz element transform-style: preserve-3d do elementu, co powoduje rozpowszechnienie wszystkich efektów 3D (takich jak wartość z perspektywy naszej perspektywy), które zostały zastosowane u góry drzewa.

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

W przypadku przeglądarki Mobile Safari sprawa jest jednak nieco bardziej zagmatwana. Technicznie rzecz biorąc, zastosowanie metody overflow-y: scroll do elementu kontenera jest skuteczne, ale kosztem możliwości przesuwania przewijanego elementu. Rozwiązaniem jest dodanie elementu -webkit-overflow-scrolling: touch, który jednocześnie spowoduje spłaszczenie właściwości perspective i uniemożliwi to zjawisko paralaksy.

Z perspektywy stopniowego ulepszania – prawdopodobnie nie jest to nadmiernie trudny problem. Jeśli nie możemy zastosować paralaksy w każdej sytuacji, aplikacja nadal będzie działać, ale dobrze byłoby znaleźć obejście tego problemu.

position: sticky na ratunek!

Można to zrobić w postaci elementu position: sticky, który umożliwia „przyklejanie” elementów do górnej części widocznego obszaru lub danego elementu nadrzędnego podczas przewijania. Specyfikacja, podobnie jak większość z nich, jest dosyć obszerna, ale zawiera też mały, pomocny klejnot:

Na pierwszy rzut oka może to nie wydawać się istotnym, ale najważniejszym elementem zdania jest, gdy odnosi się do sposobu obliczania przyklejenia elementu: „przesunięcie jest obliczane w odniesieniu do najbliższego elementu nadrzędnego z polem przewijania”. Inaczej mówiąc, odległość, jaką musi przesunąć przyklejony element (czyli tak, żeby wyglądał na dołączone do innego elementu lub widoczny obszar) jest obliczana przed zastosowaniem jakichkolwiek innych przekształceń, a nie po. Oznacza to, że podobnie jak w przykładzie przewijanym, jeśli przesunięcie zostało obliczone na 300 pikseli, pojawi się nowa możliwość użycia perspektyw (lub dowolnej innej przekształcenia) do manipulowania wartością przesunięcia 300 pikseli przed zastosowaniem jej do elementów przyklejonych.

Zastosowanie position: -webkit-sticky do elementu paralaksowego pozwala skutecznie „odwrócić” efekt wyrównywania 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, czyli w tym przypadku .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);
}

W ten sposób przywrócisz efekt paralaksy w przeglądarce mobilnej Safari.

Zastrzeżenia dotyczące przyklejonego pozycjonowania

Istnieje różnica, ale position: sticky zmienia mechanikę paralaksy. Przyklejone elementy mają przyklejać się do kontenera przewijanego, a nieprzyklejone – nie. Oznacza to, że paralaksa przyklejona jest odwrotnością układu bez:

  • W przypadku parametru position: sticky im bardziej element jest z=0, tym mniej się przesuwa.
  • Bez position: sticky im bardziej element jest z=0, tym bardziej się przesuwa.

Jeśli wydaje Ci się to abstrakcyjne, spójrz na tę demonstrację Roberta Flacka, która pokazuje, jak różne elementy zachowują się inaczej z przyklejonym pozycjonowaniem i bez niego. Aby zobaczyć różnicę, potrzebujesz Chrome Canary (w wersji 56 w momencie pisania) lub Safari.

Zrzut ekranu z perspektywą paralaksy

Prezentacja Roberta Flacka, która pokazuje, jak position: sticky wpływa na przewijanie z efektem paralaksy.

różne błędy i sposoby ich obejścia,

Jak zawsze, ale są jeszcze miejsca, które trzeba wygładzić:

  • Pomoc przyklejona jest niespójna. Obsługa jest wciąż wdrażana w Chrome, Edge nie jest w pełni obsługiwana, a w przeglądarce Firefox po połączeniu elementów przyklejonych z przekształcaniem perspektywy pojawiają się błędy malowania. W takich przypadkach warto dodać krótki kod, by dodać position: sticky (wersja z prefiksem -webkit-) tylko wtedy, gdy jest to konieczne, czyli tylko w przeglądarce mobilnej Safari.
  • Efekt nie działa po prostu w Edge. Edge próbuje obsłużyć przewijanie na poziomie systemu operacyjnego, co jest zwykle dobrym rozwiązaniem, 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ż wygląda to na przełączenie Edge na metodę przewijania inną niż system operacyjny i gwarantuje, że uwzględnia zmiany z perspektywy użytkownika.
  • „Zawartość strony właśnie stała się ogromna!” Wiele przeglądarek uwzględnia tę skalę przy podejmowaniu decyzji o wielkości treści strony, ale Chrome i Safari nie uwzględniają takiej perspektywy. Jeśli więc np. do elementu zostanie zastosowana skala 3x, możesz zobaczyć paski przewijania i podobne informacje, nawet jeśli po zastosowaniu elementu perspective element jest w pozycji 1x. Aby obejść ten problem, można skalować elementy od prawego dolnego rogu (za pomocą funkcji transform-origin: bottom right). W efekcie zbyt duże elementy mogą powiększać się do „obszaru ujemnego” (zwykle w lewym górnym rogu) obszaru, który można przewijać. W przypadku regionów z możliwością przewijania nigdy nie można zobaczyć ani przewinąć treści w obszarze negatywnym.

Podsumowanie

Paralaksa to przyjemny efekt, jeśli używasz jej rozsądnie. Jak widać, można wdrożyć go w sposób wydajny, połączyć się z przewijaniem i przeglądać w różnych przeglądarkach. Aby uzyskać pożądany efekt, trzeba dodać odrobinę matematyki i niewielką ilość schematu, więc stworzyliśmy z niego małą bibliotekę pomocniczą i przykład, które znajdziesz w repozytorium GitHub z przykładami elementów interfejsu.

Zagraj i daj nam znać, jak Ci poszło.