Potyczki na froncie, odcinek 5: Resize Observer


Tym razem opiszę rozwiązanie problemu, z którym zetknąłem się kilka miesięcy temu: zepsutego w niektórych sytuacjach wyglądu aplikacji angular w przeglądarce.

Rzeczona aplikacja ma u góry pasek zakładek w formie przycisków, których liczba i szerokość waha się w zależności od kontekstu. Dane służące do wygenerowania owego menu są pobierane asynchronicznie w czasie ładowania strony. Jeżeli całkowita szerokość paska jest większa od szerokości okna, pasek przełamuje się na dwa paski zakładek, jeden pod drugim. Pod spodem natomiast znajduje się zawartość bieżącej zakładki, która z kolei do poprawnego wyświetlania wymaga informacji o dostępnej wysokości. Owa wysokość musi być zatem wyliczana za każdym razem, gdy zmieni się wysokość okna przeglądarki lub wysokość zajęta przez pasek zakładek. Poprzednio wyliczanie wysokości dla głównej zakładki odbywało się w ngOnInit i problem polegał na tym, że zwykle w tym momencie nie był jeszcze w pełni wygenerowany pasek zakładek. Czasem jego wysokość zmieniała się ułamek sekundy później, powodując bałagan w przeglądarce.

Najprostszym rozwiązaniem tego typu błędu wydaje się opóźnienie przeliczania wysokości za pomocą metody SetTimeout. Podejście to ma jednak więcej wad niż zalet. Przede wszystkim, trzeba dobrać wielkość opóźnienia I można to zrobić metodą prób i błędów, ale nie można mieć pewności, czy dobrana w ten sposób wartość sprawdzi się w każdych warunkach. Bezpieczniej zatem sprawdzać wysokość paska zakładek wielokrotnie, aż do skutku, w stałych lub rosnących interwałach, co jednak zanieczyszcza nam aplikację dodatkową pętlą w kodzie. Istnieje o wiele bardziej eleganckie rozwiązanie.

Resize Observer, jak sama nazwa wskazuje, monitoruje wielkość wybranego elementu html i generuje event w momencie zauważenia zmiany. Wystarczy zatem podłączyć go do paska zakładek i przeliczyć wysokość zakładki w metodzie wyzwalanej eventem zamiast robić to w ngOnInit.

Dobrym sposobem na monitorowanie i odpowiednie zareagowanie na zmianę wielkości jest wykorzystanie ResizeObservera, jak w poniższym przykładzie, w postaci dyrektywy:

Stworzona dyrektywa odpowiada za podłączenie Resizeobservera, wyemitowanie eventu w momencie zmiany rozmiaru oraz prawidłowe odłączenie ResizeObservera przy zamykaniu komponentu.

Aby zapewnić kompatybilność ze starszymi przeglądarkami (IE), warto wykorzystać polyfill, który wykorzystuje inny ciekawy wynalazek – MutationObserver, pozwalający na monitorowanie zmian w elementach html we wszystkich popularnych przeglądarkach.

Opisana dyrektywa umożliwia podłączenie ResizeObserwera do dowolnego elementu html, w bardzo prosty sposób, taki jak poniżej:

Następnie wystarczy już tylko zdefiniować metodę obsługującą wywoływany event, np.:

Jeśli w odpowiedzi na wygenerowane przez ResizeObserver ma wywołać zmiany w graficznym interfejsie użytkownika, trzeba wykorzystać metodę ngZone.run(), aby Angular zauważył wprowadzoną zmianę. Standardowy cykl detekcji zmian w Angularze nie obejmuje bowiem śledzenia eventów ResizeObservera.

Obiekt przekazywany w zdarzeniu generowanym przez ResizeObserver ma właściwości opisujące współrzędne oraz wymiary obserwowanego elementu, zgrupowane w podobiekcie contentRect.

Kod źródłowy, npm

Kod źródłowy oraz instrukcję instalacji biblioteki z wyżej opisaną dyrektywą znajdziecie tutaj:

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


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 *