Ta strona używa cookies
sprawdź politykę prywatności

Rozumiem

Lazy Loading. Czym jest i najlepsze praktyki w 2019.

9/1/2019
Bartek Cis

Zwiększ wydajność swoich aplikacji, zaoszczędź na transferze sobie i użytkownikom. Użyj Lazy Loading.

Czego się dzisiaj dowiesz?

Na początku opiszę technikę przyspieszenia aplikacji w internecie czyli Lazy Loading. Będzie o  sposobie działania i podstawach teoretycznych. Po tym przejdę do wyzwań z którymi należy się zmierzyć przy implementacji i w końcu do dobrych praktyk w 2019. Na skróty:

  1. Czym jest Lazy Loading?
  2. Wyzwania przy implementacji Lazy Loading
  3. Najlepsze praktyki użycia Lazy Loading w 2019

Dlaczego o tym piszę?

Ostatnio bardzo jara mnie web performance czyli szybkość działania aplikacji webowych. Jednym z ważnych elementów tego procesu jest umiejętna praca z grafiką bo ona potrafi przekonać użytkownika do Twojej aplikacji ale równie skutecznie może go odrzucić jeśli przez nią strona ładuje się wieczność.

W pierwszym kwartale 2019 na blogu pojawi się sporo materiałów z zakresu web performance 🙂

Czym jest Lazy Loading?

Jest to technika w której przy odpaleniu strony przeglądarka nie pobiera z serwera wszystkich zasobów od razu ale robi to wtedy kiedy są one potrzebne. Stosuje się ją głównie przy ładowaniu grafik bo to właśnie one często mają największy rozmiar. Żeby jednak miało to większy sens dobrze wiedzieć jak przeglądarka “renderuje” aplikacje internetowe.

Praktyczny Przykład

Jeśli chcesz szybko zaimplementować tą technikę to polecam użycie gotowych bibliotek. Szybki przykład np. w tym filmiku:

W zasadzie mógłbyś/mogłabyś już przestać czytać ten wpis bo masz lazy-loading z głowy 😛 Poniżej jednak wytłumaczę w szczegółach ciekawsze rozwiązanie i dobrze zrozumiesz cały proces.

Critical Rendering Path

Zanim użytkownik zobaczy w przeglądarce Twoją aplikację to przeglądarka musi wykonać szereg ściśle określonych kroków. Jeśli na którymś z tych kroków nastąpi zastój to użytkownik musi czekać. Cały proces jest fajnie opisany w tym artykule.

Należy mieć na uwadze, że każda apka w internecie składa się z kilku podstawowych rodzajów zasobów: kod HTML definiujący drzewo DOM, kod CSS opisujący style czy inaczej CSSOM, kod JS nadający rozmaite funkcjonalności oraz zasoby dodatkowe jak np. grafika.

Podstawowy scenariusz jest taki, że przeglądarka interpretuje te zasoby w następującej kolejności:

  1. Budowa drzewa DOM czyli analiza HTML’a – jeśli mamy zdefiniowane jakieś grafiki np. przy pomocy tagu img to przeglądarka musi je wszystkie znać. Czyli pobrać. Tracisz czas…
  2. Budowa drzewa CSSOM czyli analiza CSS – teraz przeglądarka analizuje co ma jak wyglądać. Jeśli zdefiniujesz jakieś grafiki za pomocą background-image to trzeba na nie poczekać.
  3. Analiza skryptów JS.
  4. Stworzenie Render Tree do wyświetlenia – czyli połączenie DOM i CSSOM.
  5. Wygenerowanie Layoutu – czyli dopasowanie wyglądu do wyświetlacza.
  6. Generowanie wyglądu – czyli kolorki, obrazki itp.

Zanim zobaczysz gotowy, działający widok przeglądarka potrzebuje wszystkich zasobów. Jeśli ten widok jest duży czyli skrolujesz w nieskończoność i ma masę grafik to jest lipa… Czekasz sobie.

Łatwiej sobie to wyobrazić patrząc na ładowanie strony w Dev Toolsach np. w Chromie:

Lazy Loading

Chodzi w tym o to aby załadować tylko te zasoby/grafiki które są potrzebne w tym konkretnym momencie i zminimalizować czas oczekiwania. Jeśli użytkownik zacznie skrolować w dół na bieżąco będą pobieranie potrzebne grafiki. Logiczne?

Po co mi to?

Już chyba dobrze rozumiesz, że możesz znacząco przyśpieszyć swoją aplikację ale nie tylko o to chodzi…

Korzystają Twoi użytkownicy bo jeżeli nie potrzebują wszystkich zasobów na stronie to oszczędzają transfer na smartfonie albo mniej obciążają łącza w domu.

Korzystasz Ty jeśli Twój serwer ma np. ograniczony transfer lub dzierżawisz zasoby w zależności od ruchu na stronie.

Wyzwania przy implementacji Lazy Loading

Na razie wszystko brzmi pięknie i lekko. Zaraz popatrzysz w kod ale wcześniej jest kilka kwestii które powinieneś/aś mieć na uwadze.

Określenie co powinno być Lazy Loaded

Grafiki które są na samej górze strony powinny być widoczne zaraz po załadowaniu i nie mogą być objęte tą techniką. Jeśli to zrobisz to po wczytaniu wszystkich podstawowych zasobów odpali się odpowiedni skrypt i przeanalizuje czy załadować pliki czy nie. Prowadzi to do opóźnień i obniżenia jakości UX. Otrzymujesz efekt odwrotny do oczekiwanego…

Wszystkie loga, “hero images” itp. powinny być ładowane w konwencjonalny sposób. Dopiero to co gdzieś tam dole może użyć lazy loadingu. Zastanów się dobrze które zasoby opóźniają Twoją aplikację i mogą być wczytane w dalszej kolejności.

Pobranie z wyprzedzeniem

Kiedy użytkownik skroluje w dół chce widzieć grafiki w odpowiednim momencie. Jeśli skrypty kontrolujące moment pobrania odpalą się zbyt późno to użytkownik będzie czekał… Jeśli stanie się to np. raz to nie jest źle ale jeśli dzieje się to permanentnie to UX spada do czeluści Mordoru.

Cross-browser compatibility

Nie wszystkie zabiegi które stosujesz muszą być wspierane przez główne przeglądarki. Bardzo słabym scenariuszem jest gdy ktoś używa np. Safari na iPhonie i nie dociera do niego Twój przekaz bo… Nic nie widzi!

Content Reflow

Użycie lazy-loading wiąże się z dynamiczną manipulacją tagu img lub styli w CSS. Nieumiejętna implementacja może prowadzić do tego, że w początkowej fazie renderowania przeglądarka nie “zarezerwuje” miejsca na stronie dla grafiki. W momencie załadowania przy użyciu lazy-loadingu obraz nagle się pojawia i cała zawartość strony “przeskakuje”. UX wtedy płacze.

Podobnie może się dziać gdy użytkownik np. szybko przewija stronę w dół lub obraca urządzenie mobilne. W widoku aplikacji panuje wtedy istny chaos…

Nawet jeśli docelowej grafiki nie ma zaraz po wyrenderowaniu aplikacji to miejsce dla niej w obrębie layoutu powinno być z góry zarezerwowane.

Placeholder Image

No dobra nawet jeśli jakoś poradziłeś/aś sobie z Content Reflow to jak ktoś szybko przewinie w dół zobaczy po prostu białą lub szarą lub jakąś inną przestrzeń. Pierwsza myśl – WTF?

Podobna sytuacja może mieć miejsce gdy ktoś ma wolne łącze które nie nadąża z pobieraniem grafik.

Należy tą przestrzeń zająć jakimś obrazem zastępczym czyli placeholderem który będzie miły dla oka. W odpowiednim momencie będzie on podmieniany na docelową grafikę. Jeśli jest to estetyczne to użytkownik daje propsy 🙂

Obsługa JavaScript

Tu jest trochę gorzej bo user może wyłączyć obsługę JavaScript w przeglądarce. Jeśli ktoś robi to świadomie to ok ale w przeciwnym wypadku ta osoba nawet sobie nie zdaje sprawy jak bardzo upośledzonym użytkownikiem internetu się stała przez ten manewr.

W tym wypadku nie możemy użyć lazy loading i najrozsądniejszym chyba rozwiązaniem jest odpowiednia informacja aby włączyć JS’a bo strona nie będzie działać poprawnie. Można to zrobić np. tak:

<body>
    <noscript>
        <div class="js-disabled">
            Please enable javascript in your browser ....
        </div>
    </noscript>
    <article>
        Some HTML code
    </article>
</body>

Oczywiście ten komunikat powinien być odpowiednio ostylowany w CSS.

Ja potrzebuje rozwiązań a nie problemów!

Jak widzisz poprzednie paragrafy zmąciły wcześniej klarowny obraz. Implementacja którą zobaczysz w tym artykule będzie brała poprawkę na wszystkie te kwestie!

Bo Bedekodzić rozwiązuje problemy a nie je tworzy 🙂

Najlepsze praktyki użycia Lazy Loading w 2019

Czas na mięso artykułu. Zacznijmy od zrozumienia jaki jest stosunek drzew DOM i CSSOM do plików z grafiką.

Ładowanie grafiki – DOM

Do użycia plików graficznych DOM i HTML używają tagu img. W praktyce wygląda to tak:

<img src="www.image.com/image.jpg" alt="Some image">

Zakładając, że obraz nie ma zdefiniowanych wymiarów w CSS przeglądarka pobierze grafikę i wyrenderuje ją w jej domyślnej rozdzielczości np. 800x600px.

Jeżeli usuniesz z tagu img atrybut src wtedy cały obiekt będzie traktowany jako pusty i przeglądarka nada mu rozmiar 0x0px. Czyli w praktyce go nie będzie.

Lazy loading wykorzystuje ten fakt i w odpowiednim momencie ustawia atrybut src. Zanim jednak to się stanie definiujesz inny atrybut data-src który przechowuje adres z grafiką do tego ustawiasz klasę np. lazy która informuje, że ten obraz będzie lazy-loadowany.

<!-- Obraz przed załadowaniem -->
<img class ="lazy" data-src="www.image.com/image.jpg" alt="Some image">

<!--Obraz po załadowaniu -->
<img src="www.image.com/image.jpg" alt="Some image">

Czyli przeglądarka przy renderowaniu strony nie widząc atrybutu src przy obiekcie ignoruje go i nie pobiera grafiki. Następnie na podstawie odpowiedniej klasy i w odpowiednim momencie podmieniasz data-src na src i przeglądarka zaczyna pobieranie zasobu.

Ładowanie grafiki – CSSOM

Jeżeli chcesz ładować grafiki w CSS sytuacja jest trochę inna. Najpierw definiujesz jakiś pusty kontener który zawiera klasę lazy po czym w odpowiednim momencie ją usuwasz:

// Obraz przed załadowaniem
<div class ="some-class lazy"></div>

// Obraz po załadowaniu
<div class ="some-class"></div>

Na tym etapie kontener jest ignorowany przez przeglądarkę jednak kiedy nadasz mu style tzn. adres, wysokość i szerokość to obraz się pojawi na stronie.

.some-class {
    background-image: url(www.image.com/image.jpg);
    width: 100px;
    height: 100px;
}

.lazy {
    background-image: url();
}

Teraz cała sztuka polega na tym aby nadpisać adres klasą lazy i pustym stringiem a w odpowiednim momencie ją usunąć.

Podmiana atrybutów data-src na src

Jak nietrudno się domyślić aby załadować plik w odpowiednim momencie potrzeby jest JavaScript. Najlepszą metodą na wykrycie czy obraz znajdzie się w obrębie ekranu jest Intersection Observer API.

Intersection Observer API

To jest po prostu wypasione. JS szybko i skutecznie wykrywa jaki obiekt obecnie znajduje się w obrębie okna w przeglądarce po czym wykonuje pożądaną akcję. Koniec z uciążliwym scroll! Może to wyglądać np. tak:  

var imageObserver = new IntersectionObserver(function(entries, observer) {
    entries.forEach(function(entry) {
        if (entry.isIntersecting) {
            var image = entry.target;
            image.src = image.dataset.src;
            image.classList.remove('lazy');
            imageObserver.unobserve(image);
        }
    });
});

lazyloadImages.forEach(function(image) {
    imageObserver.observe(image);
});

Oczywiście dalej będzie działający prototyp.

W tym momencie chcę przypomnieć cross-browser compatibility. Niestety ten prototyp nie ma wsparcia w IE (RIP) co nie jest teraz wielkim problemem ale nie wprowadził tego też Apple w swojej Safari. Użytkownicy iPhonów i Mac’ów (choć Ci i tak w większości używają Chrome) to grupa której nie można zignorować. Trzeba się cofnąć o krok do tyłu i użyć klasycznych metod JS.

JavaScript Events

W tym wypadku JS wykrywa czy User nie wykonał jakiś konkretnych akcji, są to:

Scroll

Ta podstawowa która sprawdza jak “daleko” od punktu wyjściowego użytkownik się posunął. Będzie wyglądać to tak:

document.addEventListener("scroll", lazyload);

Choć bardzo przydatna to źle użyta potrafi być performance killerem…

Resize

Ta czuwa czy User nie zmienił wielkości okna przeglądarki. Co za tym idzie cały “layout” strony się zmienia i należy przeliczyć czy wykonać daną akcję czy nie.

window.addEventListener("resize", lazyload);

Orientation Change

Czyli obrócenie ekranu mobilnego.

window.addEventListener("orientationChange", lazyload);

W każdym z powyższych przypadków funkcja lazyload() robi odpowiednią podmiankę atrybutu.

Placeholder Image

To bardzo ważna kwestia bo ma bezpośredni wpływ na UX. Dobrze użyta neguje wpływ content reflow i wyświetla użytkownikowi przyjemne dla oka obrazy. Rozpatrzę dwa przypadki:

Obrazy DOM – Low Quality Image Placeholder (LQIP)

Jeśli ładujesz obrazy z poziomu DOM to najlepszą techniką jest LQIP. Jest to użycie niskiej jakości rozmytego oryginalnego obrazu który musi być przetworzony na Back-Endzie. Jeśli serwer na którym hostujesz grafiki ma taką opcję wbudowaną jak np. ImageKit to jest to bajecznie łatwe i robi się to z poziomu HTML:

See the Pen Image Blur by Bart (@bedekodzic) on CodePen.


W praktyce jest to trochę oszukiwany Lazy-Loading bo i tak każdą grafikę pobierasz od razu jednak taki przetworzony obraz jest znacznie mniejszy więc wszystko przebiega sprawniej.

No ale załóżmy, że chcesz zrobić coś takiego sam/a. 

Dominant Color Placeholder

Należy zacząć od określenia kilku dominujących kolorów w obrazie a następnie rozmycie ich np. w CSS za pomocą filter: blur(…); Robi się to na Back-Endzie np. przy pomocy biblioteki GraphicsMagick. Bardzo ciekawy artykuł na ten temat możesz znaleźć TUTAJ.

Wtyczka do WordPress

Jeśli potrzebujesz szybkiego i łatwego Lazy-Loading do WordPress’a to możesz użyć np. wtyczki Dominant Colors Lazy Loading.

Obrazy CSSOM – Static Placeholder

O ile placeholdery LQIP fajnie wzbogacają UX to jeśli nie masz dobrej implementacji na Back-Endzie sam Front-End sobie z tym nie poradzi (no chyba, że masz jakiś inny pomysł). Innym łatwiejszym i wydajniejszym rozwiązaniem jest jakiś domyślny placeholder taki sam dla wszystkich grafik. Popatrz na ten przykład dla grafik z poziomu CSSOM:

See the Pen Static placeholder by Bart (@bedekodzic) on CodePen.

Lazy loading w praktyce

W końcu przyszedł czas na działający prototyp, będą dwa różne przykłady.

Pierwszy to kod który był bazą do napisania tego artykułu i wykorzystuje placeholdery LQIP i grafikę ładowaną z poziomu DOM. Obrazy są hostowane na ImageKit’cie więc ich modyfikacja jest bajecznie łatwa.

Drugi przykład wykorzystuje metodę na obrazy z poziomu CSSOM i JavaScript jest przepisany do wersji ES6.

Pobranie z wyprzedzeniem

Analizując kod zwróć uwagę na zabieg który pobiera obraz z niewielkim wyprzedzeniem.

IntersectionObserver wykorzystuje rootMargin: “0px 0px 500px 0px”.

Scroll używa warunku el.offset().top < window.innerHeight + scrollTop + 500.

Gdzie w obydwu przypadkach 500px to “bufor bezpieczeństwa”.

Lazy Loading z DOM + LQIP 

See the Pen Lazy loading images with additional threshold – example code by ImageKit.io (@imagekit_io) on CodePen.


Lazy Loading z CSSOM + Static Placeholder

 

See the Pen Lazy Loading CSSOM by Bart (@bedekodzic) on CodePen.


Co więcej?

Tej techniki można używać do pobierania innych zasobów. Warto też sprawdzić co w tej materii mają do zaoferowania współczesne frameworki. Przykład z Angulara:

 

 

Podsumowanie

Dzisiaj opisałem lazy-loading i pokazałem kilka praktycznych przykładów. Było też trochę o tym jak renderowanegrafiki w przeglądarce.

Już wiesz jak radzić sobie z wyzwaniami przy samodzielnej implementacji i umiesz użyć technik jak np. IntersectionObserver i LQIP placeholder. 

Jeśli zależy Ci na czasie możesz używać zewnętrznych pluginów czy bibliotek.

Jak podobał Ci się ten wpis to zostaw lajka i polub moje profile na mediach społecznościowych. Może znalazłeś/aś gdzieś błąd w kodzie lub wiesz jak lepiej sobie poradzić z lazy-loading? Napisz proszę w komentarzu 🙂

Podziel się z innymi 🙂

Cześć jestem Bartek.

Na tym blogu wprowadzę Cię w tajniki Front-Endu i programowania webowego.

Warto
Social media & sharing icons powered by UltimatelySocial