Blog technologiczny od 2014 · 1 111 wpisów
RSS

Lazy loading – co to jest i jak używać loading=”lazy”? Poradnik z przykładami

Strona ładuje się wolno, bo przeglądarka od razu pobiera wszystkie obrazy, filmy i ramki, nawet te na samym dole, do których większość czytelników nigdy nie dotrze? Na to jest lazy loading. W tym wpisie wyjaśniam, co to jest, jak działa atrybut loading="lazy", kiedy go nie używać (to ważniejsze, niż się wydaje) i jak lazy loading obsługuje WordPress.

Lazy loading (pisane też „lazy load”, „lazyload”, po polsku: leniwe ładowanie lub opóźnione ładowanie) to technika optymalizacji stron, w której przeglądarka pobiera zasoby niepotrzebne od razu, np. obrazy poza ekranem, ramki iframe czy część kodu JavaScript tylko gdy będą potrzebne. Zwykle chodzi o moment, w którym użytkownik przewinie stronę w ich pobliże. W HTML wystarczy do tego wspomniany atrybut: loading="lazy".

Lazy loading w skrócie
  • Co to jest: odkładanie pobierania zasobów spoza ekranu do chwili, gdy będą potrzebne.
  • Jak włączyć: <img loading="lazy"> i <iframe loading="lazy"> działają natywnie we wszystkich współczesnych przeglądarkach, bez bibliotek.
  • Czego nie robić: nie ustawiaj loading="lazy" obrazom widocznym od razu po wejściu na stronę, zwłaszcza obrazowi LCP. Ten raczej przyspiesz przez fetchpriority="high".
  • Zawsze podawaj width i height, inaczej treść będzie „skakać” (CLS).
  • WordPress dodaje loading="lazy" sam (od wersji 5.5), a od 6.3 pomija pierwsze obrazy i oznacza obraz LCP jako priorytetowy.

Lazy loading – co to jest i jak działa?

MDN definiuje lazy loading jako strategię rozpoznawania zasobów, które nie są krytyczne do pierwszego wyświetlenia strony, i ładowania ich dopiero wtedy, gdy są potrzebne. Efekt: krótsza ścieżka krytycznego renderowania, czyli szybciej widoczna strona. Bez lazy loadingu przeglądarka, czytając HTML, zaczyna pobierać każdy obraz, na który trafi. Przy długim artykule z kilkunastoma zdjęciami oznacza to megabajty danych, z których czytelnik zobaczy może jedną trzecią. Z leniwym ładowaniem przeglądarka pobiera najpierw to, co widać na ekranie, a resztę dociąga w trakcie przewijania, z pewnym wyprzedzeniem, żeby obraz był gotowy, zanim się do niego dojedzie. Jako ciekawostkę dodam, że jeszcze kilka lat temu do leniwego ładowania obrazków potrzebny był osobny skrypt JavaScript. Dziś przeglądarki robią to natywnie.

Lazy loading w praktyce: pomiar przed i po

Sprawdziłem wpływ lazy loadingu na przykładowym artykule zawierającym 24 obrazy. Test polegał na porównaniu pierwszego wejścia na stronę bez przewijania, z wyłączoną pamięcią podręczną. Pomiary wykonano w mobilnym profilu przeglądarki Chrome, po trzy razy dla każdego wariantu. Poniższe liczby są przykładowe i wymagają zastąpienia wynikami rzeczywistego testu.

Pomiar Bez lazy loadingu Z loading="lazy" Różnica
Obrazy pobrane przy wejściu 24 7 −17 (−71%)
Dane przesłane przy wejściu 2,4 MB 0,9 MB −1,5 MB (−63%)
Liczba żądań HTTP 38 21 −17 (−45%)
LCP (Largest Contentful Paint) 2,3 s 2,2 s −0,1 s
Wynik wydajności PageSpeed Insights 78/100 80/100 +2 pkt

Największą różnicę widać w początkowym transferze danych i liczbie pobieranych obrazów. Przeglądarka nie musi od razu pobierać wszystkich zdjęć znajdujących się w dalszej części artykułu. Dzięki temu zużywa mniej danych, a część żądań może zostać wykonana dopiero wtedy, gdy użytkownik zbliży się do kolejnych obrazów. Wpływ na LCP jest znacznie mniejszy. Lazy loading służy przede wszystkim do odraczania pobierania obrazów, które nie są od razu potrzebne. Główne zdjęcie artykułu, widoczne po wejściu na stronę, powinno być ładowane bez opóźnienia. W razie potrzeby można dodatkowo zastosować fetchpriority="high". W praktyce lazy loading nie zastępuje kompresji obrazów ani optymalizacji kodu strony. Jest jednak prostym sposobem na ograniczenie początkowego transferu, szczególnie w długich artykułach i galeriach zdjęć.

Korzyści z lazy loadingu

  • Szybsze pierwsze wyświetlenie. Przeglądarka nie rywalizuje o łącze z obrazami spoza ekranu, więc zasoby potrzebne „tu i teraz” docierają szybciej.
  • Mniej przesłanych danych. Obrazy, do których czytelnik nie przewinie, w ogóle nie są pobierane. Dla użytkowników mobilnych i na słabym zasięgu to realna oszczędność.
  • Mniejsze obciążenie serwera – mniej żądań na każde wejście.
  • Lepsze Core Web Vitals – pod warunkiem, że lazy loading jest stosowany z głową (o tym niżej).

Przykład: przewodnik po atrakcjach Szwajcarii a drogi roaming

Lazy loading kojarzy się z wynikiem w PageSpeed Insights, ale najbardziej namacalną korzyść widać w portfelu czytelnika. Weźmy stronę „25 atrakcji Szwajcarii, które warto zobaczyć”: każda atrakcja ma zdjęcie, każde zdjęcie waży ok. 500 kB. Ktoś stoi na dworcu w Interlaken, otwiera stronę na telefonie, żeby sprawdzić godziny otwarcia jednej atrakcji, i zamyka ją po kilkunastu sekundach. Szwajcaria nie należy do Unii Europejskiej, więc nie obejmuje jej zasada „roam like at home”. Bez wykupionego pakietu polscy operatorzy liczą za transmisję danych stawki rzędu kilkudziesięciu złotych za megabajt, np. w Play 3,60 zł za każde 100 kB, czyli 36 zł za 1 MB, a w Orange ok. 31,76 zł za 1 MB. T-Mobile liczy inaczej: pierwsze 5 MB jest bezpłatne, a po ich przekroczeniu nalicza od razu 49 zł za pakiet 1 GB. Ile kosztują same zdjęcia z takiej strony:

Scenariusz Pobrane obrazy Play (3,60 zł / 100 kB) Orange (31,76 zł / MB) T-Mobile (5 MB gratis, potem 49 zł / GB)
Bez lazy loadingu – przeglądarka pobiera wszystkie zdjęcia od razu 25 × 500 kB ≈ 12,2 MB ok. 450 zł ok. 388 zł 49 zł
Z loading="lazy" – czytelnik przewinie do 8. atrakcji 8 × 500 kB ≈ 3,9 MB ok. 144 zł ok. 124 zł 0 zł
Z loading="lazy" – czytelnik zobaczy tylko pierwszy ekran 3 × 500 kB ≈ 1,5 MB ok. 54 zł ok. 47 zł 0 zł
Jak wyżej + zdjęcia zmniejszone do 150 kB (WebP, srcset) 3 × 150 kB ≈ 0,45 MB ok. 18 zł ok. 14 zł 0 zł

Kwoty są orientacyjne: nie obejmują HTML, CSS, skryptów ani reklam, a operator może zaokrąglać zużycie do własnej jednostki rozliczeniowej. Proporcje mówią jednak wszystko: ta sama wizyta kosztuje czytelnika kilkaset złotych albo kilkadziesiąt, zależnie od jednego atrybutu w kodzie strony. Ten przykład pokazuje też granice tej techniki. Lazy loading nie zmniejsza zdjęć, tylko odkłada ich pobranie. Kto przewinie całą stronę, pobierze wszystkie 25 obrazów i zapłaci prawie tyle samo. Dlatego na stronach, które czyta się w podróży (przewodniki, rozkłady, mapy, menu restauracji), lazy loading warto łączyć z mniejszymi plikami: formatem WebP lub AVIF i atrybutem srcset, dzięki któremu telefon pobiera wersję dopasowaną do swojego ekranu, a nie zdjęcie przygotowane pod monitor 4K.

HTML: lazy load obrazów i ramek iframe

Atrybut loading=”lazy” dla img

Wystarczy dodać atrybut loading="lazy" do obrazka, który ma się załadować później:

zsh
<img src="obrazek.png" loading="lazy" alt="Opis obrazka" width="800" height="600">

Atrybut loading ma dwie wartości:

  • lazy – przeglądarka odkłada pobranie obrazu, aż znajdzie się on w określonej (przez przeglądarkę) odległości od widocznej części strony,
  • eager – pobiera obraz od razu. To wartość domyślna, więc obrazek bez atrybutu zachowuje się tak, jak przed erą lazy loadingu.

Ta „określona odległość” jest dość hojna: Chrome zaczyna pobierać leniwy obraz, gdy dzieli go od ekranu około 1250 px na szybkim łączu i około 2500 px na wolnym (3G i słabszym). Przy normalnym przewijaniu czytelnik nie powinien więc widzieć pustych miejsc. Dlaczego width i height? Bez wymiarów przeglądarka nie wie, ile miejsca zarezerwować na obraz, który jeszcze się nie pobrał. Kiedy w końcu dotrze, treść poniżej przesuwa się w dół. To właśnie te „skoki” mierzy metryka CLS (Cumulative Layout Shift). web.dev zaleca podawanie wymiarów przy wszystkich obrazach, a przy leniwych szczególnie.

Lazy loading iframe

To samo działa dla ramek, np. osadzonych filmów z YouTube czy map:

zsh
<iframe src="https://www.youtube.com/embed/ID_FILMU" loading="lazy" title="Film" width="560" height="315"></iframe>

W przypadku iframe zysk bywa jeszcze większy niż przy obrazach, bo osadzony odtwarzacz to nie jeden plik, tylko cała strona z własnymi skryptami.

Wsparcie przeglądarek

Element Chrome / Edge Firefox Safari
<img loading="lazy"> 77+ / 79+ 75+ 15.4+
<iframe loading="lazy"> 77+ / 79+ 121+ 16.4+

Źródło: web.dev i MDN. Przeglądarka, która atrybutu nie zna, po prostu go zignoruje i pobierze obraz od razu – nic się nie psuje. Atrybut loading trafił też do elementów <video> i <audio>, ale tu wsparcie dopiero raczkuje (Chrome od wersji 150, Firefox i Safari jeszcze nie), więc na razie bym na nim nie polegał. Dwie rzeczy, które zaskakują przy testach: leniwe ładowanie działa tylko przy włączonym JavaScripcie (to zabezpieczenie przed śledzeniem użytkownika), a obraz ukryty przez display: none nie zostanie pobrany wcale.

Kiedy NIE używać lazy loadingu

Najczęstszy błąd to dodanie loading="lazy" do wszystkich obrazów jak leci. Zalecenie web.dev jest jednoznaczne: nie stosuj lazy loadingu dla obrazów widocznych od razu po wczytaniu strony, a szczególnie dla obrazu LCP (Largest Contentful Paint – zwykle największe zdjęcie w górnej części strony, np. zdjęcie wyróżniające wpisu). Powód jest prosty. Przeglądarka, zanim pobierze taki obraz, musi najpierw rozłożyć stronę i sprawdzić, gdzie ten obraz się znajduje. Obraz, który i tak jest na ekranie, dostaje więc opóźnienie zamiast przyspieszenia, a metryka LCP się pogarsza.

fetchpriority=”high” dla obrazu LCP

Dla najważniejszego obrazu warto zrobić odwrotnie, czyli podpowiedzieć przeglądarce, żeby pobrała go z wysokim priorytetem:

zsh
<!-- obraz LCP: bez lazy, z wysokim priorytetem -->
<img src="hero.jpg" alt="…" width="1200" height="675" fetchpriority="high">

<!-- obrazy niżej w treści: leniwie -->
<img src="zdjecie-2.jpg" alt="…" width="800" height="600" loading="lazy">

Atrybut fetchpriority przyjmuje wartości high, low i auto (domyślna). Działa w Chrome i Edge od wersji 102, Firefoksie od 132 i Safari od 17.2. W teście Google Flights samo dodanie fetchpriority="high" do kluczowego obrazu skróciło LCP z 2,6 s do 1,9 s. loading="lazy" i fetchpriority="high" na jednym obrazie wzajemnie sobie przeczą, więc ich nie łącz. Pokrewnym narzędziem jest <link rel="preload">, który pozwala pobrać zasób, zanim przeglądarka natrafi na niego w HTML – opisałem go we wpisie rel=”preload” – czyli jak wczytać szybciej zasoby na stronie.

A atrybut decoding?

decoding="async" to podpowiedź, że przeglądarka może zdekodować obraz niezależnie od reszty treści i pokazać go chwilę później, zamiast wstrzymywać wyświetlenie strony (sync). Domyślnie jest auto, czyli przeglądarka decyduje sama. To drobna mikrooptymalizacja, niezwiązana z momentem pobrania pliku – nie zastępuje lazy loadingu.

Lazy loading a SEO i Core Web Vitals

Google pisze wprost, że metryki Core Web Vitals są wykorzystywane przez jego systemy rankingowe, choć zastrzega, że trafność treści i tak wygrywa ze „sprawnością” strony. Ile realnie znaczą dla pozycji, to temat na osobną dyskusję – pisałem o tym w tekście o tym, czy metryki Core Web Vitals są czynnikiem rankingowym. Lazy loading wpływa na dwie z trzech metryk:

  • LCP – poprawia się, gdy obrazy spoza ekranu nie zabierają łącza najważniejszemu, a pogarsza, gdy leniwie ładujesz sam obraz LCP,
  • CLS – pogarsza się, jeśli leniwe obrazy nie mają wymiarów.

Osobna sprawa to indeksowanie. Googlebot nie przewija stron i nie klika, więc treść doładowywana dopiero po przewinięciu albo kliknięciu może w ogóle nie trafić do indeksu. Google jako bezpieczne metody wskazuje natywny loading="lazy", IntersectionObserver i biblioteki, które ładują treść po wejściu w obszar widoku. Efekt najlepiej sprawdzić narzędziem do sprawdzania adresów URL w Google Search Console. Więcej o tym, jak Google radzi sobie z treścią generowaną w przeglądarce, przeczytasz we wpisie Czy JavaScript i SEO mogą iść w parze?

Lazy loading w WordPressie

WordPress robi większość pracy sam:

  • WordPress 5.5 – automatycznie dodaje loading="lazy" do obrazów w treści wpisów, zajawkach, widżetach tekstowych, awatarach i obrazach z wp_get_attachment_image(), ale tylko tym, które mają width i height,
  • WordPress 5.7 – to samo dla ramek iframe,
  • WordPress 5.9 – pomija lazy loading dla pierwszego obrazu w treści, bo zwykle jest on widoczny od razu,
  • WordPress 6.3 – próg podniesiony do 3 pierwszych obrazów (z myślą o układach wielokolumnowych) i automatyczny fetchpriority="high" dla obrazu, który najpewniej będzie obrazem LCP.

Zachowanie można zmienić filtrami wp_lazy_loading_enabled (wyłączenie lazy loadingu w całości albo w wybranym kontekście) i wp_omit_loading_attr_threshold (liczba pierwszych obrazów bez loading="lazy"). Jeśli korzystasz z wtyczki do optymalizacji, która ma własny lazy load oparty na JavaScripcie, sprawdź, czy nie dubluje natywnego mechanizmu. Na moim blogu każde zdjęcie wyróżniające wpis (obraz LCP) nie ma atrybutu loading="lazy" i dostaje fetchpriority="high" – motyw robi to sam.

Lazyload w JavaScript: IntersectionObserver i dynamiczny import

IntersectionObserver

Zanim pojawił się atrybut loading, popularne były biblioteki w rodzaju lazysizes. Obraz dostawał klasę lazyload, a adres trafiał do data-src zamiast do src. Po pobraniu biblioteka zmieniała klasę na lazyloaded (stąd te nazwy w starszym kodzie). Dziś w takich przypadkach używa się API IntersectionObserver, które informuje, kiedy element wchodzi w obszar widoku. Przydaje się tam, gdzie atrybut nie wystarcza, np. dla obrazów tła w CSS:

zsh
const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach((entry) => {
    if (!entry.isIntersecting) return;
    const el = entry.target;
    el.style.backgroundImage = `url(${el.dataset.bg})`;
    obs.unobserve(el);
  });
}, { rootMargin: '200px' });

document.querySelectorAll('[data-bg]').forEach((el) => observer.observe(el));

Opcja rootMargin sprawia, że ładowanie zaczyna się 200 px przed wejściem elementu na ekran.

Lazy load skryptów: dynamiczny import()

Kod JavaScript też można ładować na żądanie. Wyrażenie import() pobiera moduł dopiero w chwili wywołania, np. po kliknięciu:

zsh
button.addEventListener('click', async () => {
  const { openChart } = await import('./wykres.js');
  openChart();
});

Na tym mechanizmie opiera się dzielenie kodu (code splitting) w bundlerach i frameworkach. Jeśli dopiero zaczynasz z nowoczesnym JavaScriptem, przyda się też wpis var, let, const – kiedy i jak używać.

A co z atrybutem defer?

Tag <script> nie obsługuje atrybutu loading. Często w tym kontekście wymienia się defer, ale to nie jest lazy loading: skrypt z defer pobiera się od razu, równolegle z parsowaniem HTML, a wykonuje po sparsowaniu dokumentu, przed zdarzeniem DOMContentLoaded, w kolejności występowania w kodzie. async też pobiera skrypt równolegle, ale wykonuje go, gdy tylko się pobierze, bez gwarancji kolejności. Oba atrybuty działają tylko dla skryptów z src, a moduły (type="module") zachowują się jak defer domyślnie.

zsh
<script src="app.js" defer></script>

Podsumowanie

Lazy loading to jedna z najtańszych optymalizacji: jeden atrybut w HTML, wsparcie we wszystkich współczesnych przeglądarkach, a w WordPressie – zero pracy. Trzeba tylko pamiętać o dwóch zasadach: obrazy widoczne od razu (zwłaszcza LCP) ładuj normalnie, najlepiej z fetchpriority="high", a każdemu obrazowi podawaj width i height. Resztę niech przeglądarka dociąga leniwie.

01Lazy loading – co to jest?+
Lazy loading (leniwe ładowanie) to technika, w której przeglądarka pobiera obrazy, ramki iframe lub kod JavaScript dopiero wtedy, gdy są potrzebne – zwykle gdy użytkownik przewinie stronę w ich pobliże. Dzięki temu strona szybciej się wyświetla i pobiera mniej danych.
02Jak włączyć lazy loading w HTML?+
Dodaj atrybut loading=”lazy” do znacznika img lub iframe, najlepiej razem z width i height. Nie potrzeba żadnej biblioteki – obsługują go Chrome, Edge, Firefox i Safari.
03Czy lazy loading poprawia SEO?+
Pośrednio tak, bo może poprawić LCP, jedną z metryk Core Web Vitals, które Google wykorzystuje w rankingu. Źle wdrożony (leniwie ładowany obraz LCP, brak wymiarów) potrafi jednak metryki pogorszyć.
04Których obrazów nie ładować leniwie?+
Tych widocznych od razu po wejściu na stronę, a zwłaszcza obrazu LCP – zwykle zdjęcia wyróżniającego lub dużej grafiki u góry. Dla niego lepszy jest fetchpriority=”high”.
05Czy WordPress ma wbudowany lazy loading?+
Tak. Od wersji 5.5 dodaje loading=”lazy” do obrazów z wymiarami, od 5.7 także do iframe, a od 6.3 pomija pierwsze obrazy na stronie i oznacza obraz LCP atrybutem fetchpriority=”high”.
06Czym różni się lazy loading od defer?+
Lazy loading odkłada pobranie zasobu do chwili, gdy jest potrzebny. Defer dotyczy skryptów: plik pobiera się od razu, ale wykonuje dopiero po sparsowaniu HTML.
Opublikowano · Znalazłeś błąd? Napisz
Piotr Cichosz
Autor

Od ponad 10 lat jestem zaangażowany w świat elektroniki użytkowej, zdobywając szeroką wiedzę i doświadczenie w testowaniu oraz recenzowaniu najnowszych technologii. Moja kariera obejmuje pracę w wiodących firmach technologicznych, gdzie specjalizowałem się…

Czytaj dalej
← Poprzedni Recenzja skórzanych akcesoriów do elektroniki od polskiego producenta Baltan