18 maja 2019

Lazy load (opóźnione ładowanie zdjęć i obrazów)

Kategoria: Programowanie
Tagi: dla profesjonalistów,
Autor: Paweł Mansfeld

Lazy load (opóźnione ładowanie zdjęć i obrazów)

W tym poradniku napiszemy od zera mechanizm opóźnionego (czy odłożonego) ładowania zdjęć. Inne nazwy to „leniwe” ładowanie zdjęć lub po prostu lazy load.

Skrypt jest dosyć prosty, jednak niepoprawne wdrożenie potrafi zrujnować wydajność przegladarki dlatego w tym wpisie napiszemy skrypt a potem wdrożymy tzw. throttling, który będzie czuwał nad tym aby scroll listener nie próbował w każdej milisekundzie sprawdzać wysokości widoku i wczytywać zdjęć. Zapewni to odpowiednią wydajność i wygodę użytkownika.

Czego nie potrafię stworzyć, tego nie rozumiem.

Richard Feynman

Po co wdrażać i jak działa Lazy-load?

Na początku wytłumaczymy sobie o co chodzi z tym lazy-loadem. Wyobraźmy sobie stronę internetową np. wpis na blogu, który zawiera wiele zdjęć. Załóżmy, że zdjęcia są zoptymalizowane i trafnie dobrano formaty zapisu i mają poprawną wielkość do strony internetowej.

Problem w tym, że nawet jak zdjęcia będą idealnie zoptymalizowane ich spora ilość spowoduje, że strona mimo to będzie się wczytywać wolno i zanim wszystkie zdjęcia się nie ściągną, strona nie będzie interaktywna. Nie będzie można zjechać w dół, tekst będzie niewidoczny i nie będą działały linki. Rozwiązanie tego problemu jest konieczne do zdobycia 100 punktów w Page Speed Insights:

Rozwiązaniem na ten problem (jak można się domyślić) jest lazy-load. Wykorzystuje on prosty fakt, mianowicie, na ekranie widzimy co najwyżej wstęp do artykułu i pierwsze zdjęcie. Cała reszta może się ściągnąć w tle dopiero jak zjedziemy do tych zdjęć. To powoduje, że zdjęcia , które będą oglądane dopiero za jakiś czas nie będą blokować już na starcie wczytywania się kluczowych elementów tej strony czyli tekstu, fontów i zdjęć, które znajdują się na samej górze.

Metoda z wykorzystaniem JavaScript

W tym rozdziale pokażę jak działa tradycyjna metoda polegająca na podmianie atrybutu src za pomocą JavaScript.

HTML

Standardowe zdjęcie jest zamieszczane w taki sposób:

<img src=”obrazek.jpg” alt=”opis obrazka”>

Najwydajniejszym sposobem na zablokowanie wczytywania zdjęcia jest po prostu usunięcie wartości atrybutu src. Informacja o tym jakie ma się wczytać zdjęcie możey być przechowywana w atrybucie data. Zdefiniujmy go jako data-src. Aby móc łatwo wybrać te zdjęcia z DOM dodajmy klasę. Np. lazy-load. Pozostaje nam jeszcze dodanie placeholdera, czyli obrazka który będzie przechowywał miejsce na wczytane zdjęcie. Niektórzy decydują się na załadowanie animowanego loadera.

Dla mnie jest to marnotrawstwo zasobów dlatego polecam użyć np. białego jednego piksela. Poprawnie zaimplementowany lazy-load ma działać transparentnie, czyli tak, że użytkownik nie zobaczy ani luki ani tego loadera. Ponieważ odwołanie się do zewnętrznego zasobu też marnuje komunikację HTTP w obie strony, wykorzystamy możliwość umieszczenia zdjęcia za pomocą danych w URL, które przechowują zaszyfrowany obrazek w formacie base64. Np. przezroczysty piksel w formacie GIF wygląda tak:

data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

Cały znacznik, który będzie współpracował z naszym lazy-load będzie wyglądał tak:

<img class="lazy-img" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="obrazek.jpg" alt="opis obrazka">

JavaScript

Cała magia jak zwykle dzieje się w JavaScript. będziemy korzystać ze standardowych metod jQuery dlatego wystarczy nam kompilacja jQuery Slim.

Aby sprawnie korzystać z listenerów stwórzmy sobie funkcję lazyLoad(). W niej będziemy mieli pętlę each, która będzie sprawdzała wszystkie zdjęcia z klasą lazy-image czy tak czasem nie znajdują się na tyle wysoko aby podmienić wartość atrybutu src na wartość przechowywaną w data-src.

Sprawdzenie tego czy zdjęcie ma się już załadować jest banalnie proste. Jeżeli offset.top (czyli taki jakby margines od górnej krawędzi strony) będzie mniejszy niż wysokość widoku przeglądarki, plus „przewinięty” obszar widoku przeglądarki, plus margines jaki dajemy przeglądarce na załadowanie zdjęcia za wczasu aby właśnie użytkownik nie widział przerwy. U nas ten margines będzie wynosił 300 pikseli.

Po wszystkim warto posprzątać, po podmianie zdjęcia usuńmy klasę lazy-image aby nie dokonywać niepotrzebnych obliczeń.:

function lazyLoad(){
$('.lazy-img').each(function(){
if ($(this).offset().top < window.innerHeight + window.pageYOffset + 300) {
$(this).attr('src', $(this).data('src'));
$(this).removeClass('lazy-img');
}
})
};

Teraz czas na stworzenie czujnika, który przy przewijaniu będzie odpalał naszą funkcję. Większość „programistów” zrobiłaby

$(document).on('scroll', function() {
lazyLoad();
});

To jest bardzo niewydajne, bo jak wiadomo scroll trwa w czasie. W ciągu jednego przewinięcia myszką funkcja wykona się kilkadziesiąt razy. To niepotrzebnie marnuje moc obliczeniową urządzenia. Zróbmy prosty throttling:

var eventTimeout;
var eventThrottler = function () {
if ( !eventTimeout ) {
eventTimeout = setTimeout(function() {
eventTimeout = null;
lazyLoad();
}, 1000);
}
};

Dzięki temu zabiegowi, funkcja lazyLoad() będzie się włączać najczęściej raz na sekundę. Teraz wystarczy dodać listenera w jQuerowym stylu:

$(document).on('scroll', function() {       
      eventThrottler();
});

Testowanie

Sprawdźmy czy działa.

Jak widać skrypt mówiąc kolokwialnie daje radę. Przy „normalnym” czytaniu artykułu użytkownik nie ma szans zauważyć pobierania się zdjęć w tle. Przy szybkim przewijaniu widać jak puste pola są wypełniane zdjęciem.

Lazy Load tła – czyli leniwe ładowanie background image

To wszystko wygląda bardzo prosto w przypadku zwykłych zdjęć. A jak się mają sprawy w przypadku tła i efektów parallax? Nic nie stoi na przeszkodzie aby i w tym przypadku skorzystać z tego samego podejścia.

<section class="jumbotron text-center lazy-bg" data-background="tlo.jpg">

Teraz wystarczy w naszej funkcji lazyLoad() dopisać instrukcję odpowiedzialną za podmianę stylu:

 $('.lazy-bg').css('background-image','url("'+ $('.lazy-bg').data('background') +'")'); 

W przypadku wykorzystywania gotowych wtyczek do uzyskania efektu parallax pamiętajmy, że będziemy musieli po tej instrukcji ponownie zainicjować animację:

$('.parallax').parallax(); 

Dokładnie ten sam problem możemy mieć w przypadku karuzel OWL czy innych revolution sliderów. Wystarczy ponownie te skrypty zainicjować. Dla owl carousel było to chyba:

$('.owl-carousel').trigger('refresh.owl.carousel');

Efekty wizualne przy lazy load i animowany loader

Jeżeli zależy nam na płynnych efektach w przypadku gdyby z jakiegoś powodu zdjęcia nie pobrały by się na czas, możemy użyć animowanego spinnera w ramach placeholdera. Wówczas zamiast jednego piksela w formie data możemy użyć takiej lub podobnej grafiki:

To może być pozytywne pod kątem User Experience, ponieważ grafika ta daje do zrozumienia, że zaraz powinno się tutaj coś pojawić.

Oprócz tego, możemy nadać zdjęciom domyślne opacity o wartości 0, oraz ustawić dla nich transition-duration na około 500ms lub więcej – kwestia gustu. Po podmianie atrybutu możemy to opacity ustawić na wartość 1 i zdjęcia będą się przejściowo pojawiać. Aby uzyskać efekt stopniowego pojawiania się grafiki w przypadku tła, możemy użyć triku z wewnętrznym box-shadow. Domyślnie:

box-shadow: inset 0 0 0 3000px rgba(230,230,230,1);

To nałoży ciemną warstwę na tło. W kombinacji z transition-duration możemy później płynnie pokazać tło:

box-shadow: inset 0 0 0 3000px rgba(230,230,230,0);

Lazy-load w czystym JavaScript

Choć w kodzie używam jQuery, nic nie stoi na przeszkodzie aby to samo rozwiązanie napisać w czystym JavaScript lub jak kto woli: w VanillaJS. Wystarczy metody each, attr, i removeClass zamienić na je natywne odpowiedniki czyli forEach, setAttribute, classList.remove.

Staram się aby wszystkie proponowane rozwiązania były praktyczne i otwarte na zmiany, dlatego gdzie się tylko da używam jQuery, który i tak jest dawno dodany przez inne funkcjonalności na stronach internetowych lub przez sam CMS.

Natywny lazy-load z wykorzystaniem HTML

Do najpopularniejszej przeglądarki Chrome doszła funkcja odpowiedzialna za natywne wsparcie mechanizmu lazy load. Wystarczy do zdjecia dodać atrybut loading=”lazy” aby mechanizm opóźnionego ładowania działał bez „trików” wykonywanych w JavaScript.

To rozwiązuje wiele problemów, przede wszystkim oczyszcza kod ze zbędnego atrybutu data-src oraz dodatkowego JavaScriptu. Wsparcie tej techniki jak na razie zaimplementowane jest w przeglądarkach Desktopowych i mobilnych z rodziny Chrome oraz Edge. Mechanizm będzie wkrótce spierany także w Firefox:

Wsparcie natywnego lazy-load w przeglądarkach. Źródło: Caniuse.

Kod HTML zdjęcia wygląda tak:

<img loading="lazy" src="https://przykladowa-domena.pl/images/obrazek.png">

W tej technice mamy jeszcze inną wartość dla atrybutu loading, mianowicie: eager:

<img loading="eager" src="https://przykladowa-domena.pl/images/obrazek.png">

Taki kod spowoduje, ze przeglądarka natychmiast załaduje zdjęcie – nie będzie czekać aż pojawi się w bierzącym widoku (ang. viewport) przeglądarki.

Dodatkową zaletą tej metody jest to, że możemy jej użyć także dla elementów iframe, czyli przykładowo dla filmików YouTube:

<iframe src="https://przykladowa-domena.pl" loading="lazy"></iframe>

Lazy-Load w popularnych CMS

Mechanizm lazy-load możemy łatwo wdrożyć do naszej strony internetowej za pomocą gotwych modułów i wtyczek.

WordPress

Joomla!

PrestaShop

Podsumowanie

Lazy-load to technika zwiększania wydajności stron internetowych poprzez odłożenie w czasie pobierania wszystkich zasobów. To konieczny krok, jeżeli na naszej stronie jest wiele zdjęć a chcemy idealnie zoptymalizować stronę i uzyskać 100 punktów w PageSpeed Insights. Trwają prace nad natywnym wsparciem tej funkcjonalności przez wszystkie przeglądarki.

Źródła

https://developers.google.com/web/tools/lighthouse/audits/offscreen-images?utm_source=lighthouse&utm_medium=unknown

https://github.com/whatwg/html/pull/3752

https://web.dev/native-lazy-loading/

Oceń artykuł na temat: Lazy load (opóźnione ładowanie zdjęć i obrazów)
Średnia : 4.6 , Maksymalnie : 5 , Głosów : 18


 

Odpowiedz lub skomentuj

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *




Komentarze

włodek

20 października 2019

A czy można prosić o wytłumaczenia, (jak krowie na granicy,) co i gdzie wstawić w przypadku Prestashop 1.7.

Paweł Mansfeld

20 października 2019

W PrestaShop 1.6 i 1.7 w widokach szablonu i w widokach modułów (pliki zakończone .tpl lub .php) są miejsca w kodzie które wyglądają tak: <img class="replace-2x img-responsive" src=" Takie coś na bank można znaleźć w pliku products-list.tpl. Jeżeli będą jakieś dodatkowe klasy to się nie przejmujemy i je zostawiamy. Najprościej, ściągnąć sobie cały sklep i np. za pomocą programu Notepad++ wyszukać to miejsce we wszystkich plikach. I teraz tak:

  • podmienić w tej linijce src=" na data-src="
  • dodać ten src z pustym pikselem lub src z animowanym gifem
  • dodać klasę lazy-img do obecnych klas
Po podmianie powinno wyjść coś takiego: <img class="replace-2x img-responsive lazy-img" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="
I to wszystko, jeśli chodzi o pliki szablonu.

Teraz w pliku custom.js naszego szablonu dodajemy kod JS tego lazy loadera lub robimy sobie własny moduł który doda go do stopki. Od razu ostrzegam, że w PrestaShop często używane są revolution-slidery, owl-slidey i flexslidery. Należy zmodyfikować też kod tych pluginów wywołując naszą funkcję lazyLoad(); w zdarzeniach "before" i "after". Chodzi o to aby grafiki się podmieniły przy animacji karuzeli bo te wtyczki generują poszczególne slajdy dynamicznie (generowane są widoczne slajdy, poprzedni i następny). Mam nadzieję, że pomogłem.

włodek

20 października 2019

Dziękuję bardzo za podpowiedzi. Od jutra zacznę walczyć z tematem.

 

Wykryto brak połączenia z Internetem.