Potyczki na froncie, odcinek 2: IntersectionObserver


Obserwowanie zmian widoczności wybranych elementów na stronie to coraz częściej spotykany mechanizm nowoczesnych aplikacji internetowych, umożliwiający przede wszystkim tak zwany lazy loading, czyli automatyczne doczytywanie dodatkowych treści w miarę przeglądania i przewijania strony przez użytkownika. W poprzednim odcinku przedstawiłem przykładową implementację opartą o ScrollEvent, natomiast poniżej opowiem o nowszym i w większości sytuacji optymalnym rozwiązaniu – IntersectionObserver.

IntersectionObserver

IntersectionObserver pozwala na monitorowanie widoczności elementu strony internetowej w aktualnych wersjach wszystkich współczesnych przeglądarek internetowych. Nie da się z niego bezpośrednio skorzystać we wciąż żywym Internet Explorerze, ale można obejść ten problem, wykorzystując dedykowany Polyfill.

Przykładowe użycie IntersectionObservera wygląda następująco:

W pierwszej kolejności definiujemy obiekt konfiguracji składający się z następujących elementów:

  • Root: element, względem którego mamy monitorowac widoczność. Przy braku zdefiniowania tego elementu lub ustawieniu go jako null, rootem staje się okno przeglądarki.
  • RootMargin: szerokość marginesu wokół obserwowanego elementu wliczanego przy wyliczaniu widoczności. Pozwala na zdefiniowanie marginesów po każdej stronie w postaci ilości pikseli lub % w sposób znany z css. Domyślnie marginesy mają wartośc 0. Przykładowo, ustawienie górnego marginesu o szerokości 50px umożliwia otrzymanie notyfikacji w sytuacji, gdy użytkownik przewijając stronę zbliży się do obserwowanego elementu na odległość 50 pikseli.
  • Treshold: poziom lub tablica kilku poziomów widoczności (liczb z przedziału 0 – 1), po przekorczeniu których nasz IntersectionObserver ma wywołać callback. Przykładowo treshold o wartości 0 powoduje wywołanie callbacku, gdy jakakolwiek część elementu stanie się widoczna, natomiast treshold o wartości 1 spowoduje wywołanie eventu dopiero wtedy, gdy element stanie się całkowicie widoczny.

Następnie budujemy nasz IntersectionObserver, podając jako parametry callback oraz obiekt konfiguracji.

Na koniec, za pomoca metody observe musimy poinformować nasz IntersectionObserver o tym, który element HTML ma on obserwować. Potem pozostaje już tylko czekać na wywołania metody callback poprzez IntersectionObserver.

Poniżej zaprezentuję bibliotekę wykorzystującą IntersectionObserver do monitorowania widoczności elementów w sposób nieco prostszy i bardziej zgodny z filozofią Angulara.

Nasza funkcja callback każdorazowo w evencie IntersectionObserverEntry otrzymuje kilka interesujących wartości, z których najistotniejsze to:

  • isIntersecting: boolean – true, jeśli element stał się widoczny (jeśli przekroczył dany próg widoczności),
  • intersectionRatio: number – wartość między 0 a 1 określająca stopień widoczności,
  • target: HTMLElemement – obserwowany element, którego dotyczy notyfikacja (ważny parametr, gdy ten sam obserwer obserwuje więcej niż jeden element).

IntersectionObserver jako observable

IntersectionObserver można użyć w prosty sposób opisany powyżej, ale w aplikacji Angularowej da się to zrobić w zdecydowanie bardziej ‘Angularowy’ sposób, z wykorzystaniem RxJs i dyrektywy. Opiszę bibliotekę, która robi to w taki właśnie sposób.

Najważniejszą częścią biblioteki jest serwis udostępniający funkcjonalność IntersectionObservera w postacji obiektu Observable

Nasz obiekt Obsevable wywołuje IntersectionObserver, aby dla wybranego elementu HTML emitować notyfikacje o zmianie jego widoczności.

Oprócz standardowego zestawu parametrów mamy zdefiniowany dodatkowy opcjonalny parametr stopWhenVisible, który umożliwia automatyczne zakończenie obserwowania danego elementu natychmiast,  gdy ów element stanie się widoczny. Takie zachwanie jest preferowane na przykład w sytuacji, kiedy potrzebujemy załadować konkretną treść w konkretnym miejscu na stronie po dodarciu użytkownika do danego miejsca i potem nie chcemy już w dalszym ciągu monitorowac tego elementu.

Można zasubskrybowac powyższy obiekt Observable, aby monitorować widoczność danego elementu, ale prościej i wygodniej jest wykorzystać do tego dyrektywę.

IntersectionObserver jako dyrektywa

Stworzenie dyrektywy wykorzystującej dostarczony przez serwis obiekt Observable sprowadza się do wywołania subskrybcji z odpowiednimi parametrami i jej zakończenia w ngOnDestroy.

Aby monitorować widoczność za pomocą powyższej dyrektywy, wystarczy dodać w atrybutach wybranego elementu HTM dyrektywę wraz z jej parametrem określającym callback, tak jak w poniższym przykładzie:

Dodatkowo można podać opcjonalne parametry konfiguracji IntersectionObservera [intersectionRootMargin], [intersectionRoot], IntersectionTreshold] oraz [stopWhenVisible]=’true’

Optymalizacja

Opisany wyżej kod to przykład prostej implementacji tworzącej osobny IntersectionObserver dla każdego obserwowanego elementu HTML.

Jeśli potrzebujemy w ten sam sposób (tj. z tą samą konfiguracją) monitorować wiele elementów, lepiej jest użyć do tego celu jednego wspólnego obiektu IntersectionObserver. Optymalizuje to wykorzystanie zasobów przez naszą aplikację, ale wymaga zaimplementowania dodatkowej logiki.

Dodatkowe mechanizmy muszą wówczas sprawdzać, czy istnieje już IntersectionObserver o zadanych parametrach i w zaleznosci od tego dodawać element do istniejącej subskrybcji albo tworzyć nowy InetrsectionObserver. Podobnie, w ngOnDestroy należy sprawdzić, czy dany IntersectionObserwer obserwuje więcej niż jeden element i w zależności od tego przestać obserwować wybrany element lub całkowicie zakończyć subskrybcję i działanie IntersectionObserwera.

Implementację obserwacji wielu elementów tym samym IntersectionObserwerem ułatwia zastosowanie opratora RxJS share(), który dba o zamknięcie współdzielonej subskrybcji w odpowiednim momencie.

RxJS pozwala łatwo zaimplementować dodatkowy typ opcjonalnej optymalizacji, polegający na ograniczeniu częstotliwości emitowania eventów. W tym celu do opisanej wyżej dyrektywy można dodać  dodatkowy parametr throttleTime określający minimalny interwał w milisekundach i podłączyć operator o tej samej nazwie do subskrybowanej obsevable.

Kod i npm

Kompletny kod w wersji uwzględniającej wspomniane wyżej optymalizacje jest dostępny na portalu Github pod adresem:

https://github.com/piotr-zysk/LabIntersectionObserver/tree/master/projects/pz-intersection-observer

Można również pobrać go w postaci pakietu npm:

W celu zapewnienia kompatybilności ze starszymi przeglądarkami udostępniona biblioteka wykorzystuje polyfill.

Podsumowanie

IntersectionObserver umożliwia monitorowanie widoczności elementów na stronie w prosty i efektywny sposób. W porównaniu z klasycznymi rozwiązaniami opartymi o ScrollEvent, IntersectionObserver działa zdecydowanie mniej obciążając stację roboczą, co na starszych komputerach może przekładać się na płynne działanie aplikacji. Przede wszystkim jednak nie wymaga on implementowania złożonej, skomplikowanej i podatnej na błędy logiki. Nie musimy przejmować się różnymi aspektami monitorowania widoczności. Wystarczy podłączyć IntersectionObserver, najwygodniej poprzez zaprezentowaną wyżej dyrektywę, a za prawidłową obsługę we wszystkich przypadkach optymalnie zadbają za nas wbudowane mechanizmy.


Piotr Zyśk

O Piotr Zyśk

Od 2019 roku jako programista fullstack w Atenie zgłębiam głównie technologie .Net i Angular. Wcześniej przez kilkanaście lat zajmowałem się szeroko pojętym wsparciem administracyjno-programistycznym w dla dużej sieci call-center. W wolnym czasie lubię wykorzystywać swój komputer na różne sposoby, np. jako studio do tworzenia muzyki lub urządzenie do symulacji rajdów i wyścigów samochodowych. Często również można spotkać mnie w podolsztyńskich lasach na rowerze, z aparatem fotograficznym.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *