chevron-left chevron-right

Cykle życia strony internetowej. Jak reagować na zmiany za pomocą JavaScript?

Kiedy mówimy o cyklach życia, zazwyczaj mamy przed oczami: dorastanie, bycie dorosłym i starzenie się. W przypadku stron i aplikacji internetowych działających w przeglądarkach można by użyć luźnego powiązania z tymi cyklami. Oczywiście strona nie będzie dorastała ani starzała się w trakcie jednej sesji użytkownika. Brzmi kontrowersyjnie? Na pewno tak, ale więcej można się dowiedzieć w dalszej części tekstu.

Cykle życia strony internetowej w przeglądarce

Strona internetowa opiera się na HTML i może wykorzystywać JavaScript. Dlatego też, istnieje takie pojęcie jak event, czyli zdarzenie które zostało wywołane w kodzie JavaScript. Każda strona/aplikacja internetowa otwarta w przeglądarce może reagować na trzy takie eventy związane z istnieniem strony:

  1. DOMContentLoaded,
  2. load,
  3. beforeunload/unload.

Te trzy zdarzenia zawsze się pojawiają w przypadku poprawnie działającej strony. Każdy z nich jest odpalany w innym momencie. Dzięki czemu, jesteśmy w stanie odpowiednio zareagować w naszych skryptach JavaScript. Z własnego doświadczenia wiem, że najczęściej się korzysta z obsługi zdarzenia load. Dlaczego? Wyjaśnienie znajdziesz poniżej.

Zdarzenie DOMContentLoaded

Zdarzenie DOMContentLoaded jest uruchamiane w momencie wczytania kodu HTML całej strony. To również oznacza, że tutaj nie musimy czekać na załadowanie się wszystkich skryptów JS, obrazków czy stylów CSS. Jest ono rzadko wykorzystywane w procesie tworzenia strony internetowej czy też aplikacji internetowej. Przykładem wykorzystania takiego eventu może być: sprawdzenie czy serwer zwrócił stronę z błędem po wysłaniu formularza wyświetlonego w iframe, aby następnie móc to obsłużyć poza iframe'em. Przykładowy kod obsługujący to zdarzenie wygląda następująco:

const popup = window.document.querySelector('#popup');
const form = popup.querySelector('form');
const iframe = popup.querySelector('iframe');
const checkIsSuccess = (event) => {
    const errorMessage = iframe.contentDocument.querySelector('.error-message');

    if (errorMessage) {
        window.document.body.classList.add('has-error');

        return;
    }

    window.document.body.classList.remove('has-error');
    window.document.body.classList.add('has-success');
    popup.remove();
}

iframe.contentDocument.addEventListener('DOMContentLoaded', checkIsSuccess, false);

form.submit();

Zdarzenie load

To zdarzenie jest uruchamiane w momencie, gdy wszystkie zależności pliku HTML, takie jak obrazki, skrypty i style zostały pobrane przez przeglądarkę (błąd pobrania któregokolwiek z plików nie powstrzymuje przeglądarki przed odpaleniem tego zdarzenia). Wcześniej wspomniałem, że zdarzenie load jest najczęściej wykorzystywane, dzieje się tak dlatego, że wtedy dopiero jesteśmy w stanie mieć pewność, że wszystkie nasze zależności zostały pobrane i możemy urochomić odpowiednie skrypty JS które wzbogacą nasz interfejs na przykład o funkcjonalność galerii zdjęć. Nasłuchiwać zdarzenie load możemy na kilka sposobów:

Skrypt samowykonujący się

(function() {
    // kod który ma się uruchomić po pełnym załadowaniu strony
})();

Jest to najpopularniejsze podejście. Sam z niego najczęściej korzystam.

Nasłuchiwanie w addEventListener

const initGallery = () => { /* kod uruchamiający galerię po załadowaniu strony */ };

document.addEventListener('load', initGallery, false);

Przypięcie obsługi zdarzenia bezpośrednio pod własność obiektu document

window.document.onload = initGallery;

Jest to najrzadziej stosowane podejście, bo w ten sposób podepniemy tylko jedną funkcję pod zdarzenie.

Ciekawostka

Zdarzenie load może być wywołane nie tylko na obiekcie document, ale również na elementach HTML związanych z <img /> oraz <video />. Dzięki temu jesteśmy w stanie wykryć moment w którym dane zdjęcie bądź plik wideo się załaduje na stronie. Do obsługi tego zachowania polecam stosowanie następującego podejścia:

const image = window.document.querySelector('img');
const doSomething = () => {};

image.onload = doSomething;

Zdarzenie beforeunload/unload

Zdarzenie beforeunload jest uruchamiane w momencie próby zamknięcia strony w zakładce przeglądarki. Za jego pomocą możemy wykonać wiele akcji które w końcowym zamyśle pozwolą na przykład, na zachowanie stanu aplikacji lub upewnienie się, że użytkownik na pewno chce zaniechać pewnej czyności na stronie. Co ważne, obsługę tego zdarzenia możemy sobie zapewnić tylko poprzez podpięcie własnej funkcji do własności obiektu document.

window.onbeforeunload = function () {
    // jeśli użytkownik potwierdzi że chce zapisać stan aplikacji
    if (confirm("Czy chcesz zapisać obecny stan formularza na później?")) {
        const inputs = document.getElementsByTagName('input');

        // zapisz wartości pól z formularza w localStorage
        localStorage['input-jeden'] = inputs[0].value;
        localStorage['input-dwa']   = inputs[1].value;
    }
};

Powyższe zagadnienie już poruszałem swego czasu na blogu, gdzie prezentowałem prosty skrypt JavaScript do obsługi zapisu stanu formularzy

Bonusowe zdarzenia

Oprócz powyższych zdarzeń związanych bezpośrednio z procesem ładowania treści na stronie możemy również obsłużyć szereg innych zdarzeń. Między innymi:

  • visibilitychange - za pomocą tego zdarzenia możemy wykryć moment w którym użytkownik przełączył się na inną zakładkę w przeglądarce lub zminimalizował okno przeglądarki. Na blogu poruszałem już to zagadnienie w artykule dotyczącym synchronizacji wideo między dwiema zakładkami przeglądarki.
  • resize - to zdarzenie jest uruchamiane w momencie zmiany szerokości okna przeglądarki. Bywa przydatne, lecz jego użycie wymaga odpowiedniego podejścia jeśli chcemy obliczać odpowiednią pozycję elementu w interfejsie aplikacji czy strony internetowej. Ten event odpala się bardzo często co może powodować spadki wydajności w przypadku nieprawidłowej obsługi tego zdarzenia.
  • transitionend - to zdarzenie jest bardzo ciekawe, bo niejako łączy ze sobą świat CSS i JavaScript. Za jego pomocą możemy obsłużyć moment, w którym zakończy się animacja przejścia między dwiema wartościami wybranej własności CSS. W CSS ustawiamy transition: background 0.5s linear; jako styl wybranego elementu, a następnie możemy to obsłużyć w JS za pomocą: element.addEventListener('transitionend', doSomething, false);.
  • observery - to poniekąd nie są zdarzenia, lecz całe API udostępnione w specyfikacji HTML5. Zaliczamy do nich: MutationObserver czy też ResizeObserver. MutationObserver jest używany gdy chcemy sprawdzić czy nastąpiły zmiany w drzewie DOM, np. dodaliśmy nowy element HTML do drzewa lub zmieniliśmy wartość atrybutu HTML. ResizeObserver wykorzystujemy gdy chcemy obsługiwać zmianę wymiarów danego elementu i na tej podstawie wykonywać jakieś akcje w aplikacji. Więcej na temat observerów możesz przeczytać w tekście dotyczącym obserwacji zmian wartości atrybutów elementów DOM.

Podsumowanie

Tworząc strony internetowe czy aplikacje internetowe jest bardzo ważne, aby wiedzieć co się dzieje z interfejsem w danym momencie. Dzięki temu możemy reagować na różne sytuacje i często dzięki temu możemy zoptymalizować działanie interfejsu poprzez wyłączanie/włączanie pewnych funkcjonalności. Mam nadzieję, że tym wpisem udało mi się zaspokoić Twoją ciekawość i będziesz w stanie wykorzystać tą wiedzę w swoich projektach.

Tekst ten dedykuję mojemu Tacie, który odszedł w minione wakacje.