Frontend

ResizeObserver API: responsywność w jeszcze wydajniejszej odsłonie

responsywność aplikacji

Czy zastanawialiście się kiedyś w jaki sposób można dynamicznie dopasowywać elementy oraz reagować na zmiany ich rozmiarów w aplikacji, tak żeby jak najlepiej sprostać oczekiwaniom jej użytkowników? Do tej pory mieliśmy tylko kilka opcji, np. rozwiązujące podstawowe problemy z responsywnością elementów mediaQueries czy też globalne eventy onresize lub ich odpowiednik addEventListener resize. Stosowanie tych rozwiązań w przypadku bardziej zawiłych funkcjonalność często wymagało ‘hakowania’ – nie wspominając o utracie wydajności aplikacji.

Na szczęście na horyzoncie pojawiło się ResizeObserver API – nowe API, które jest uważane za bardziej wydajne i zdecydowanie łatwiejsze w zastosowaniu niż eventy onresize. Sprawdźmy, co możemy osiągnąć stosując to rozwiązanie.

Piotr Michalczuk. JavaScript Developer w Merixstudio. Frontend developer specjalizujący się w Angularze i Reacie. Kodowanie rozpoczął trzy lata temu od kursu CodersLab (React Bootcamp, polecam!), a teraz doskonali wiedzę podczas pracy w Merixstudio, jak również w ramach lokalnych meetupów oraz realizując swoje własne projekty developerskie. Jeśli aktualnie nie siedzi przy kompie to zapewne jest na szlaku – uwielbia trekking górski.


Kilka słów wstępu

ResizeObserver API pozwala na bardzo dokładne i bezpośrednie monitorowanie wymiarów pojedynczego elementu. Początkowo wsparcie przeglądarek dla tego API było ograniczone jedynie do Chrome (wersja 64) i Firefox (wersja 69), dlatego zastosowanie go na produkcji nie wchodziło w grę. Na szczęście w 2020 roku wsparcie tej funkcjonalności wśród przeglądarek poprawiło się na tyle, że możemy ze spokojem zacząć używać ResizeObserver API częściej. Oczywiście zawsze jest jakieś „ale”. W tym przypadku mamy do czynienia z odwiecznym problemem niewspierania starszych przeglądarek. Rozwiązaniem może być użycie łatki – choć musimy pamiętać, że przy jej zastosowaniu możemy nie mieć dostępu do wszystkich opcji.

resizeobserver api

Źródło: caniuse.com

Na co komu ResizeObserver aPI

Po tym krótkim wprowadzeniu pora zastanowić się gdzie i kiedy możemy użyć ResizeObserver API. Jak już wspomniałem, to API zostało wprowadzone, aby pokonać dotychczasowe słabości CSS’owych mediaQueries oraz eventów onresize. W związku z tym, ResizeObserver API radzi sobie świetnie w następujących przypadkach:

  • zmiana rozmiaru fontu w zależności od wysokości lub szerokości elementu, w którym znajduje się dany tekst,
  • aktualizacja wymiarów wykresów,
  • dodawanie lub usuwanie elementów w zależności od wymiaru elementu bez konieczności odwoływania się do całego okna,
  • zwiększenie responsywności tabeli,
  • idealne umiejscowienie strzałki slidera z dynamiczną zawartością w oparciu o wysokość pojedynczego elementu.

Myślę, że po zapoznaniu się z kodem i przyjrzeniu się “z czym to się je”, sami będziecie w stanie podać jeszcze kilka przykładów. Bierzmy się więc do pracy!

Z kodu wzięte: przykład 1

Jeśli na bieżąco śledzicie bloga Just Join IT, na pewno wiecie już co nieco o składni obserwatorów. Sam w jednym z poprzednich artykułów wyjaśniałem jak działa IntersectionObserver, a na blogu Merixstudio pisałem o MutationObserver. Nie martwcie się jednak jeżeli nie czytaliście żadnej z tych publikacji, bo zaraz wyjaśnię Wam działanie obserwatorów krok po kroku.

Interfejs

Aby stworzyć ResizeObserver, używamy konstruktora ResizeObserver(). Następnie musimy zdefiniować funkcję zwrotną, która zostanie wywołana jedynie w przypadku gdy zmienią się wymiary obserwowanego przez nas elementu.

const resizeObserver = new ResizeObserver((entries, observer) => {
    entries.forEach(entry => console.log(entry))
});

Tym sposobem stworzyliśmy bardzo prosty obiekt ResizeObserver i przekazaliśmy do jego konstruktora funkcję zwrotną. Ta ostatnia wymaga zresztą własnych parametrów: entries, czyli wpisów oraz samego obserwatora. Wpisy są niczym innym jak tablicą zawierającą obiekty ResizeObserverEntry – do tego co dokładnie się w nich znajduje przejdziemy za chwilę. Drugi parametr jest natomiast odnośnikiem do obserwatora. Możemy go użyć później, np. do zaprzestania obserwowania danego elementu w przypadku gdy jakiś szczególny warunek zostanie spełniony.

Wszystko fajnie, ale pewnie zauważyliście już, że czegoś nam tutaj jednak brakuje. I macie całkowitą rację – musimy przecież zacząć obserwować jakiś element!

Obserwacja elementu

Ten etap jest banalnie prosty: musimy jedynie użyć metody observe() na utworzonej przez nas wcześniej instancji ResizeObserver. Metoda ta, podobnie jak poprzednie, przyjmuje dwa argumenty. W pierwszym możemy przekazać zarówno jeden jak i kilka elementów, które chcemy obserwować, natomiast w drugim przekazujemy parametr opcji.

Co ciekawe, te ostatnie są w przypadku ResizeObserver API nie tylko nieskomplikowane, ale też opcjonalne. W momencie pisania tego artykułu jedyną dostępną opcją jest box, dzięki której możemy określić box model obserwowanego elementu. Domyślnie wybrany jest content-box, ale w razie potrzeby możemy zmienić go na border-box lub device-content-box. Pamiętajcie jednak żeby zawsze sprawdzić, którego modelu w danym momencie potrzebujecie – doświadczenie nauczyło mnie, że zły wybór może prowadzić do nerwowego i żmudnego debugowania.

Sam kod jest bardzo prosty:

resizeObserver.observe(elements, options)

Jeśli chcemy przestać obserwować dany element, używamy resizeObserver.unobserve(element). W przypadku większej liczby obserwowanych elementów i chęci zatrzymania wszystkich procesów, możemy użyć metody resizeObserver.diconnect().

Informacje od obserwatora

Zgodnie z W3C Working Draft, powiadomienie o zmianie wymiarów elementu zostanie wywołane tylko w przypadku spełnienia jednego z kilku warunków. Spójrzmy na kilka przykładowych sytuacji:

  • Obserwacja nastąpi kiedy obserwowany element zostanie umieszczony w lub usunięty z drzewa DOM.
  • Obserwacja nastąpi kiedy właściwość display obserwowanego elementu zostanie ustawiona na none.
  • Obserwacja nie nastąpi w przypadku elementów non-replaced inline.
  • Obserwacje nie będą wywoływane przez CSS’owe transformacje.
  • Obserwacja nastąpi w gdy rozpocznie się renderowanie elementu, a jego wielkość będzie inna niż 0,0.

Możemy teraz stworzyć przykładowy element, aby zobaczyć jak wygląda powiadomienie otrzymywane z naszego obserwatora i czego możemy się z niego dowiedzieć. Zaczynamy od elementu HTML, który przekazujemy do naszego ResizeObservera używając metody observe():

<p class="observed">Hello World!</p>

resizeObserver.observe(element);

Patrząc na kod naszego resizeObserver możemy zauważyć, że używamy console.log() dla każdego pojedynczego elementu wejściowego. Tak wygląda to w konsoli:

Całkiem nieźle, prawda? W contentRect możemy znaleźć całą masę przydatnych informacji na temat wymiarów danego elementu.

To chyba wszystko jeśli chodzi o podstawowy sposób wykorzystania ResizeObserver API. Teraz zamiast console.log() wystarczy dodać własną funkcję i gotowe!

Z kodu wzięte: przykład 2

Podzielę się z Wami jeszcze jednym z życia wziętym przykładem zastosowania ResizeObserver API. Tym razem będę się opierać na React oraz Hookach. Dobrze byście mieli już wiedzę i pierwsze doświadczenia z tymi rozwiązaniami – dzięki temu po prostu lepiej zrozumiecie kolejne akapity.

Problem

W projekcie, przy którym niedawno pracowałem, napotkaliśmy ciekawy problem: złożone designy przedstawiające dwurzędową siatkę z galerią, która w przy danej szerokości elementu miała przekształcać się w karuzelę. Szerokość ta była bardzo łatwo osiągana przy zmianie widoku z pionowego na poziomy.

Pomyślałem, że dobrym sposobem na podejście do tego problemu może być użycie ResizeObserver API. Chciałem stworzyć rozwiązanie, które będzie można zastosować nie tylko w przypadku tego konkretnego komponentu, ale również w różnych innych miejscach w projekcie, w których będzie ono odpowiednie. Aby osiągnąć mój cel, zacząłem pracę nad stworzeniem własnego Hooka.

Rozwiązanie

Zacznijmy od utworzenia prostej funkcji przyjmującej dwa argumenty. Pierwszy argument to element, który chcemy obserwować – w tym przypadku nazwiemy go ref. Drugi to wielkość graniczna, którą będziemy obserwować w contentRect danego elementu.

export function useResizeObserver(ref, size) { };

Następnie dodajemy useState Hook aby okiełznać stan w oraz zwrócić wynik naszej funkcji. Nazwijmy zmienną stanu isDesktop i niech będzie to zmienna typu boolean. Ustawiamy domyślny stan jako false:

Import { useState } from ‘react’;

export function useResizeObserver(ref, size) {
    const [isDesktop, setIsDesktop] = useState(false);

    return isDesktop;
 };

Kolejny krok to poradzenie sobie z logiką ResizeObserver poprzez dodanie useEffect Hook.

import { useEffect, useState } from ‘react’;

export function useResizeObserver(ref, size) {
 const [isDesktop, setIsDesktop] = useState(false);

 useEffect(() => {
   const observeTarget = ref.current;
   const resizeObserver = new ResizeObserver(entries => (
     entries[0].contentRect.width < size ? setIsDesktop(false) : setIsDesktop(true)
   ));


   resizeObserver.observe(observeTarget);
 }, [ref, size ]);

 return isDesktop;
};

W React używamy useRef Hook by odwołać się do wirtualnego elementu DOM – tak zrobimy również w naszym przypadku. Tworzymy nową zmienną observeTarget i przypisujemy do niej właściwość .current z naszego ref’a. Jest to element, który będziemy obserwować.

Następnie definiujemy naszego resizeObservera i jego funkcję zwrotną. W funkcji zwrotnej sprawdzamy tylko pierwszy element wejściowy. Obserwując jeden element, możemy sprawdzić czy wielkość naszego elementu osiągnęła podaną przez nas wartość stosując proste if else. Jeśli warunek zostanie spełniony, aktualizujemy nasz stan za pomocą setIsDesktop.

W ten sposób stworzyliśmy Hooka, którego będziemy mogli użyć wielokrotnie w naszej aplikacji w ten sposób:

const isDesktop = useResizeObserver(galleryRef, 1140);

Stosować ResizeObserver API czy nie, oto jest pytanie

ResizeObserver API można z powodzeniem uznać za kolejny obserwator gotowy do użycia na produkcji. Dzięki niemu, tworzenie responsywnych elementów stało się dużo łatwiejsze i wydajniejsze. Jeśli potrzebujecie twardych danych na ten temat, udało mi się dotrzeć do stwierdzenia, że “wydajność resizeObserver może być nawet o 10 razy lepsza niż rozwiązania takie jak onresize eventy”. Nie ma co się zastanawiać, czas zacząć stosować to rozwiązanie!


Zdjęcie główne artykułu pochodzi z unsplash.com.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://geek.justjoin.it/resizeobserver-api-responsywnosc-w-jeszcze-wydajniejszej-odslonie/" order_type="social" width="100%" count_of_comments="8" ]