ResizeObserver informuje o zmianie rozmiaru elementu.
Zanim pojawił się interfejs ResizeObserver, trzeba było dołączyć detektor do zdarzenia resize dokumentu, aby otrzymywać powiadomienia o każdej zmianie wymiarów widocznego obszaru. W detektorze zdarzeń trzeba było następnie określić, które elementy zostały dotknięte tą zmianą, i wywołać odpowiednią procedurę, aby odpowiednio zareagować. Jeśli po zmianie rozmiaru elementu potrzebne były jego nowe wymiary, trzeba było wywołać funkcję getBoundingClientRect() lub getComputedStyle(), co mogło spowodować thrashing układu, jeśli nie zadbano o grupowanie wszystkich odczytów i wszystkich zapisów.
Nie obejmowało to nawet przypadków, w których elementy zmieniają rozmiar bez zmiany rozmiaru okna głównego. Na przykład dołączanie nowych elementów podrzędnych, ustawianie stylu display elementu na none lub podobne działania mogą zmienić rozmiar elementu, jego elementów równorzędnych lub jego elementów nadrzędnych.
Dlatego ResizeObserver jest przydatnym elementem podstawowym. Reaguje na zmiany rozmiaru dowolnego z obserwowanych elementów, niezależnie od przyczyny zmiany.
Zapewnia też dostęp do nowego rozmiaru obserwowanych elementów.
Interfejs API
Wszystkie interfejsy API z sufiksem Observer, o których wspomnieliśmy powyżej, mają prostą konstrukcję. ResizeObserver nie jest wyjątkiem. Tworzysz obiekt ResizeObserver i przekazujesz wywołanie zwrotne do konstruktora. Do wywołania zwrotnego przekazywana jest tablica obiektów ResizeObserverEntry – po jednym wpisie na obserwowany element – która zawiera nowe wymiary elementu.
var ro = new ResizeObserver(entries => {
for (let entry of entries) {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
}
});
// Observe one or multiple elements
ro.observe(someElement);
Kilka szczegółów
Co jest zgłaszane?
Ogólnie rzecz biorąc, a
ResizeObserverEntry
zgłasza pole zawartości elementu za pomocą właściwości o nazwie
contentRect, która zwraca obiekt
DOMRectReadOnly. Pole zawartości to pole, w którym można umieścić treści. Jest to pole obramowania pomniejszone o dopełnienie.
Pamiętaj, że chociaż ResizeObserver zgłasza zarówno wymiary contentRect, jak i dopełnienie, to obserwuje tylko contentRect.
Nie myl contentRect z ramką ograniczającą elementu. Ramka ograniczająca, zgłaszana przez getBoundingClientRect(), to pole, które zawiera cały element i jego elementy podrzędne. Wyjątkiem są SVG, w przypadku których ResizeObserver zgłasza wymiary ramki ograniczającej.
Od Chrome 84 ResizeObserverEntry ma 3 nowe właściwości, które zapewniają bardziej szczegółowe informacje. Każda z tych właściwości zwraca obiekt ResizeObserverSize zawierający właściwość blockSize i właściwość inlineSize. Te informacje dotyczą obserwowanego elementu w momencie wywołania zwrotnego.
borderBoxSizecontentBoxSizedevicePixelContentBoxSize
Wszystkie te elementy zwracają tablice tylko do odczytu, ponieważ w przyszłości mają obsługiwać elementy, które mają wiele fragmentów, co występuje w scenariuszach wielokolumnowych. Obecnie te tablice będą zawierać tylko 1 element.
Obsługa tych właściwości na platformie jest ograniczona, ale Firefox obsługuje już 2 pierwsze.
Kiedy jest to zgłaszane?
Specyfikacja przewiduje, że ResizeObserver powinien przetwarzać wszystkie zdarzenia zmiany rozmiaru przed malowaniem i po układzie. Dzięki temu wywołanie zwrotne ResizeObserver jest idealnym miejscem do wprowadzania zmian w układzie strony. Ponieważ przetwarzanie ResizeObserver odbywa się między układem a malowaniem, spowoduje to tylko unieważnienie układu, a nie malowania.
Rozumiem
Możesz się zastanawiać, co się stanie, jeśli zmienisz rozmiar obserwowanego elementu w wywołaniu zwrotnym ResizeObserver. Odpowiedź jest prosta: od razu wywołasz kolejne wywołanie zwrotne. Na szczęście ResizeObserver ma mechanizm, który pozwala uniknąć nieskończonych pętli wywołań zwrotnych i zależności cyklicznych. Zmiany będą przetwarzane w tej samej klatce tylko wtedy, gdy element o zmienionym rozmiarze znajduje się głębiej w drzewie DOM niż najpłytszy element przetworzony w poprzednim wywołaniu zwrotnym.
W przeciwnym razie zostaną one odroczone do następnej klatki.
Aplikacja
Jedną z rzeczy, które umożliwia ResizeObserver, jest implementowanie zapytań o media dla poszczególnych elementów. Obserwując elementy, możesz imperatywnie definiować punkty przerwania projektu i zmieniać style elementu. W poniższym
przykładzie drugi boks
zmieni promień obramowania w zależności od jego szerokości.
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
entry.target.style.borderRadius =
Math.max(0, 250 - entry.contentRect.width) + 'px';
}
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));
Innym ciekawym przykładem jest okno czatu. Problem, który pojawia się w typowym układzie rozmowy od góry do dołu, to pozycjonowanie przewijania. Aby nie mylić użytkownika, warto, aby okno pozostawało na dole rozmowy, gdzie pojawiają się najnowsze wiadomości. Dodatkowo każda zmiana układu (np. zmiana orientacji telefonu z poziomej na pionową lub odwrotnie) powinna osiągnąć ten sam efekt.
ResizeObserver umożliwia napisanie jednego fragmentu kodu, który obsługuje oba scenariusze. Zmiana rozmiaru okna to zdarzenie, które ResizeObserver może przechwycić z definicji, ale wywołanie appendChild() również zmienia rozmiar tego elementu (chyba że ustawiono overflow: hidden), ponieważ musi on zrobić miejsce na nowe elementy. Mając to na uwadze, wystarczy kilka wierszy, aby osiągnąć pożądany efekt:
const ro = new ResizeObserver(entries => {
document.scrollingElement.scrollTop =
document.scrollingElement.scrollHeight;
});
// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);
// Observe the timeline to process new messages
ro.observe(timeline);
Całkiem nieźle, prawda?
W tym miejscu mogę dodać więcej kodu, aby obsługiwać sytuację, w której użytkownik ręcznie przewinął w górę i chce, aby przewijanie zatrzymało się na tej wiadomości, gdy pojawi się nowa.
Innym przypadkiem użycia jest dowolny element niestandardowy, który ma własny układ.
Do czasu pojawienia się ResizeObserver nie było niezawodnego sposobu na otrzymywanie powiadomień o zmianie jego wymiarów, aby można było ponownie ułożyć jego elementy podrzędne.
Wpływ na interakcję do kolejnego wyrenderowania (INP)
Interakcja do kolejnego wyrenderowania (INP) to dane, które określają ogólną responsywność strony w odniesieniu do interakcji użytkowników. Jeśli INP strony mieści się w progu "dobry" (czyli 200 milisekund lub mniej), można powiedzieć, że strona jest niezawodnie responsywna na interakcje użytkownika.
Chociaż czas potrzebny na wykonanie wywołań zwrotnych zdarzeń w odpowiedzi na interakcję użytkownika może znacząco wpłynąć na całkowite opóźnienie interakcji, nie jest to jedyny aspekt INP, który należy wziąć pod uwagę. INP uwzględnia też czas potrzebny na kolejne wyrenderowanie interakcji. Jest to czas potrzebny na wykonanie pracy związanej z renderowaniem, która jest wymagana do zaktualizowania interfejsu użytkownika w odpowiedzi na interakcję.
W przypadku ResizeObserver jest to ważne, ponieważ wywołanie zwrotne, które wykonuje instancja ResizerObserver, następuje bezpośrednio przed pracą związaną z renderowaniem. Jest to celowe, ponieważ należy wziąć pod uwagę pracę, która odbywa się w wywołaniu zwrotnym, ponieważ jej wynik najprawdopodobniej będzie wymagał zmiany interfejsu użytkownika.
Zadbaj o to, aby w wywołaniu zwrotnym ResizeObserver wykonywać jak najmniej pracy związanej z renderowaniem, ponieważ nadmierna praca związana z renderowaniem może spowodować opóźnienie ważnych zadań w przeglądarce. Jeśli na przykład jakakolwiek interakcja ma wywołanie zwrotne, które powoduje uruchomienie wywołania zwrotnego ResizeObserver, aby zapewnić jak najpłynniejsze działanie, wykonaj te czynności:
- Upewnij się, że selektory CSS są jak najprostsze, aby uniknąć nadmiernej pracy związanej z ponownym obliczaniem stylów. Ponowne obliczanie stylów odbywa się bezpośrednio przed układem, a złożone selektory CSS mogą opóźniać operacje układu.
- Unikaj wykonywania w wywołaniu zwrotnym
ResizeObserverjakichkolwiek działań, które mogą powodować wymuszone ponowne renderowanie. - Czas potrzebny na zaktualizowanie układu strony zwykle rośnie wraz z liczbą elementów DOM na stronie. Chociaż jest to prawdą niezależnie od tego, czy strony używają
ResizeObserver, praca wykonywana w wywołaniu zwrotnymResizeObservermoże stać się znacząca wraz ze wzrostem złożoności strukturalnej strony.
Podsumowanie
ResizeObserver jest dostępny we wszystkich głównych
przeglądarkach
i zapewnia skuteczny sposób monitorowania zmian rozmiaru elementów na poziomie elementu. Uważaj tylko, aby nie opóźniać zbytnio renderowania za pomocą tego zaawansowanego interfejsu API.