Paralaksyna

Wprowadzenie

Paralaksa jest ostatnio szalona, dlatego zobacz przykłady:

Jeśli ich nie znasz, są to witryny, w których struktura wizualna strony zmienia się podczas przewijania. Standardowo elementy w obrębie strony skalują, obracają się lub poruszają proporcjonalnie do pozycji przewijania na stronie.

Strona demonstracyjna z paralaksą
Nasza strona demonstracyjna w pełni z efektem paralaksy

Niezależnie od tego, czy lubisz strony z paralaksą, czy nie, możesz stwierdzić, że są to czarna dziura w skuteczności. Dzieje się tak, ponieważ przeglądarki są zoptymalizowane pod kątem sytuacji, w której podczas przewijania strony pojawiają się nowe treści na górze lub na dole ekranu (w zależności od kierunku przewijania). Ogólnie rzecz biorąc, przeglądarki działają najlepiej, gdy podczas przewijania strony nic się nie zmienia. W przypadku witryn z paralaksą rzadko się to dzieje, ponieważ wiele razy duże elementy wizualne na stronie zmieniają się, co powoduje, że przeglądarka ponownie wyświetla całą stronę.

Rozsądnie jest uogólniać witrynę z paralaksą w ten sposób:

  • Elementy tła, które podczas przewijania w górę i w dół zmieniają swoją pozycję, obrót i skalę.
  • Zawartość strony, np. tekst lub mniejsze obrazy, przewijana w typowy sposób od góry do dołu.

Omówiliśmy już wydajność przewijania oraz sposoby poprawy responsywności aplikacji. Omawiamy ten artykuł na tej podstawie. Zachęcamy do zapoznania się z tymi informacjami.

Czy Twoja witryna z przewijaniem paralaksowym wymaga kosztownych odświeżeń, czy istnieją inne sposoby maksymalizacji skuteczności. Przyjrzyjmy się dostępnym opcjom.

Opcja 1. Użyj elementów DOM i pozycji bezwzględnych

Wydaje się, że jest to domyślne podejście większości ludzi. Na stronie znajduje się kilka elementów, a po wywołaniu zdarzenia przewijania następuje kilka wizualnych aktualizacji w celu ich przekształcenia.

Jeśli uruchomisz oś czasu w Narzędziach deweloperskich w trybie klatek i przewiniesz dookoła, zauważysz, że wykonywane są kosztowne operacje renderowania pełnoekranowego. Jeśli będziesz często przewijać, możesz zobaczyć kilka zdarzeń przewijania w ramach 1 klatki, z których każdy uruchamia układ.

W Narzędziach deweloperskich w Chrome bez zdarzeń przewijania w tle.
Narzędzia deweloperskie z dużymi obrazami i wieloma układami wyzwalanymi zdarzeniami w 1 klatce.

Pamiętaj, że przy ustawieniu 60 kl./s. (co odpowiada typowej częstotliwości odświeżania monitora 60 Hz), wystarczy trochę ponad 16 ms na wykonanie wszystkich czynności. W pierwszej wersji wprowadzamy aktualizacje wizualne za każdym razem, gdy wystąpi zdarzenie przewijania. Jednak jak wspominaliśmy w poprzednich artykułach o prostszych, mniej skomplikowanych animacjach z metodą requestAnimationFrame i wydajności przewijania, nie zbiega się to z harmonogramem aktualizacji przeglądarki, więc albo w niej pomijamy klatki, albo w nich poświęcamy dużo pracy. Może to spowodować, że witryna będzie nienaturalna, a użytkownicy rozczarowani i niezadowoleni kocięta.

Przenieśmy kod aktualizacji ze zdarzenia przewijania do wywołania zwrotnego requestAnimationFrame i po prostu przechwyć wartość przewijania w wywołaniu zwrotnym zdarzenia przewijania.

Jeśli powtórzysz test przewijania, zapewne zauważysz niewielką, ale niezbyt dużą poprawę. Dzieje się tak dlatego, że operacja układu wyzwalana przez przewijanie nie jest aż tak kosztowna, ale w innych przypadkach może być. Teraz w przypadku każdej ramki wykonujemy co najmniej jedną operację układu.

Narzędzia deweloperskie w Chrome z odrzuconymi zdarzeniami przewijania.
Narzędzia deweloperskie z dużymi obrazami i wieloma układami wyzwalanymi zdarzeniami w 1 klatce.

Teraz możemy obsługiwać nawet 10 zdarzeń przewijania na klatkę, ale podczas uruchamiania wywołania zwrotnego requestAnimationFrame i przeprowadzania aktualizacji wizualnych przechowujemy tylko najnowszą wartość. Chodzi o to, że nie próbujesz wymuszać aktualizacji wizualnych za każdym razem, gdy otrzymujesz zdarzenie przewijania, i przesyłasz do przeglądarki prośbę o utworzenie odpowiedniego okna do ich wykonania. Jesteś słodki?

Główny problem przy zastosowaniu tego podejścia (requestAnimationFrame lub nie) polega na tym, że zasadniczo mamy jedną warstwę dla całej strony, a przenoszenie tych elementów wizualnych wymaga dużych (i drogich) przeprojektowań. Zwykle obrazowanie oznacza operację blokującą (choć zmienia się), co oznacza, że przeglądarka nie może nic więcej zrobić i często przekraczamy budżet ramki wynoszący 16 ms, przez co nic się nie dzieje.

Opcja 2. Użyj elementów DOM i przekształceń 3D

Zamiast stosować położenie bezwzględne innym podejściem, możemy zastosować przekształcenia 3D do elementów. W takiej sytuacji elementy z zastosowanymi przekształceniami 3D otrzymują nową warstwę na element, a w przeglądarkach WebKit często powoduje to również przełączenie się na kompozytor sprzętowy. Z kolei w opcji 1 mieliśmy jedną dużą warstwę na stronie, którą trzeba było ponownie wyrenderować, gdy cokolwiek się zmieniło, a całe malowanie i komponowanie były obsługiwane przez procesor.

Oznacza to, że w przypadku tej opcji wygląda to inaczej: potencjalnie mamy jedną warstwę dla każdego elementu, do którego zastosujemy przekształcenie 3D. Jeśli od tego momentu zrobimy więcej przekształceń elementów, nie będzie trzeba ponownie malować warstwy, a GPU będzie zajmować się przenoszeniem elementów i komponowaniem ostatecznej strony razem.

Użytkownicy często po prostu korzystają z metody -webkit-transform: translateZ(0); i obserwują niewiarygodną poprawę wydajności. Obecnie są pewne problemy:

  1. Nie jest on zgodny z różnymi przeglądarkami.
  2. Aby wymusić działanie strony przeglądarki, każdy przekształcony element tworzy nową warstwę. Duża liczba warstw może obniżyć wydajność, dlatego używaj go oszczędnie.
  3. Została wyłączona dla niektórych portów WebKit (czwarty punkt od dołu).

Zachowaj ostrożność podczas korzystania z tłumaczenia 3D, ponieważ jest to tymczasowe rozwiązanie problemu. W idealnej sytuacji przekształcenie 2D w 3D byłoby bardzo podobne do tych w 3D. Przeglądarki rozwijają się w fenomenalnym tempie, więc mamy nadzieję, że wcześniej to będzie widoczne.

Pamiętaj też, by unikać malowania wszędzie tam, gdzie to możliwe, i po prostu przenosić istniejące elementy na stronie. Na przykład w witrynach z paralaksą typowym podejściem jest stosowanie elementów div o stałej wysokości i zmienianie ich położenia w tle, aby uzyskać odpowiedni efekt. Niestety oznacza to, że za każdym razem trzeba retuszować element, co może prowadzić do obniżenia wydajności. Zamiast tego lepiej utworzyć element (w razie potrzeby umieść go w elemencie div za pomocą atrybutu overflow: hidden) i po prostu go przetłumaczyć.

Opcja 3. Użyj płótna o stałej pozycji lub WebGL

Ostatnią opcją, którą rozważymy, jest użycie płótna o stałym położeniu z tyłu strony, na które rysujemy przekształcone obrazy. Na pierwszy rzut oka może się ono wydawać mało skuteczne, ale w rzeczywistości ma kilka zalet:

  • Ponieważ mamy tylko jeden element, obszar roboczy, nie wymagamy już tak dużej pracy z kompozytorem.
  • Mamy do czynienia z pojedynczą mapą bitową z akceleracją sprzętową.
  • Interfejs Canvas2D API doskonale sprawdza się w przypadku przekształceń, które chcemy wprowadzić, dzięki czemu łatwiej jest zarządzać programowaniem i konserwacją.

Użycie elementu canvas daje nam nową warstwę, ale to tylko jedna warstwa, podczas gdy w opcji 2 otrzymaliśmy nową warstwę dla każdego elementu z zastosowanym przekształceniem 3D. Oznacza to większe obciążenie pracą na komponowanie wszystkich tych warstw. Jest to również najbardziej kompatybilne rozwiązanie ze względu na różne implementacje przekształceń w różnych przeglądarkach.


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

Takie podejście sprawdza się, gdy mamy do czynienia z dużymi obrazami (lub innymi elementami, które można łatwo wpisać w powietrzu). Praca z dużymi blokami tekstu byłaby z pewnością trudniejsza, ale w zależności od witryny może się okazać najodpowiedniejszym rozwiązaniem. Jeśli musisz zająć się tekstem w obszarze roboczym, należałoby użyć metody interfejsu API fillText, ale wiąże się to z kosztem ułatwień dostępu (właśnie zrastrowano tekst w bitmapę). Trzeba więc radzić sobie z zawijaniem wierszy i mnóstwem innych problemów. Jeśli możesz tego uniknąć, naprawdę warto to zrobić – możesz skorzystać z powyższego podejścia do przekształceń, które stosujesz w przypadku Twojej reklamy.

Skoro idziemy to jak najdalej, nie ma powodu, by zakładać, że działanie paralaksy powinno zostać wykonane wewnątrz elementu canvas. Jeśli przeglądarka obsługuje WebGL, możemy użyć WebGL. Kluczem jest to, że WebGL zapewnia najbezpośrednią drogę wszystkich interfejsów API do karty graficznej, dlatego najprawdopodobniej jest kandydatem do osiągnięcia 60 kl./s, zwłaszcza jeśli efekty działania witryny są złożone.

Twoja natychmiastowa reakcja może wynikać z tego, że WebGL jest przestarzały lub nie jest wszechobecny pod względem pomocy, ale jeśli używasz np. kodu Three.js, zawsze możesz wrócić do elementu canvas, a kod zostanie wyodrębniony w spójny i przyjazny sposób. Wystarczy użyć narzędzia Modernizr, aby sprawdzić, czy interfejs API jest obsługiwany:

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

Na koniec sugerujemy, że jeśli nie przepadasz za dodawaniem dodatkowych elementów do strony, zawsze możesz użyć elementu canvas jako elementu tła zarówno w Firefoksie, jak i w przeglądarkach opartych na silniku WebKit. Oczywiście nie jest to uniwersalne, więc jak zwykle należy traktować to z rozwagą.

Wybór należy do Ciebie

Głównym powodem, dla którego deweloperzy domyślnie wybierają elementy absolutnie umiejscowione w odróżnieniu od pozostałych opcji, może być po prostu wszechstronność wsparcia. Jest to do pewnego stopnia niezrozumiałe, ponieważ starsze kierowane przeglądarki mogą nie obsługiwać renderowania. Nawet we współczesnych współczesnych przeglądarkach używanie absolutnie pozycjonowanych elementów nie musi prowadzić do dobrej skuteczności.

Przekształcenia, zwłaszcza 3D, umożliwiają bezpośrednią pracę z elementami DOM i umożliwiają uzyskanie stałej liczby klatek. Kluczem do sukcesu w tym przypadku jest unikanie malowania wszędzie tam, gdzie to możliwe, i próbowanie przenoszenia elementów. Pamiętaj, że sposób, w jaki przeglądarki WebKit tworzą warstwy, nie musi wiązać się z innymi silnikami przeglądarki, więc przetestuj go, zanim zdecydujesz się na zastosowanie tego rozwiązania.

Może to być najlepsze rozwiązanie, jeśli chcesz wyświetlać reklamy tylko w najlepszych przeglądarkach i umożliwiać renderowanie witryny za pomocą obiektów canvas. Jeśli używasz biblioteki Three.js, przełączanie i przełączanie się między mechanizmami renderowania powinno być bardzo łatwe, w zależności od potrzeb.

Podsumowanie

Oceniliśmy kilka sposobów radzenia sobie z miejscami z paralaksą – od elementów w absolutnie pozycji po używanie płótna o stałej pozycji. Sposób implementacji zależy oczywiście od tego, co chcesz osiągnąć i od konkretnego projektu, ale zawsze warto wiedzieć, że masz inne możliwości.

Niezależnie od wybranej metody – nie zgaduj, tylko próbuj.