Sprawdzone metody dotyczące leniwego ładowania

Chociaż leniwe ładowanie obrazów i filmów przynosi pozytywne i mierzalne korzyści w zakresie wydajności, nie jest to trudne zadanie. Jeśli się pomylisz, może to skutkować niezamierzonymi konsekwencjami. Dlatego warto pamiętać o następujących kwestiach.

Bądź na bieżąco

Leniwe ładowanie wszystkich zasobów medialnych na stronie za pomocą JavaScriptu może wydawać się kuszące, ale musisz się od tego powstrzymać. Żadne elementy znajdujące się w części strony widocznej na ekranie nie powinny być ładowane leniwie. Takie zasoby należy uznać za kluczowe i dlatego powinny być ładowane normalnie.

Leniwe ładowanie opóźnia ładowanie zasobów do momentu zakończenia działania interfejsu DOM i rozpoczęcia wykonywania skryptów. W przypadku obrazów w części strony widocznej po przewinięciu nie ma to znaczenia, ale najważniejsze zasoby w części strony widocznej na ekranie powinny być ładowane za pomocą standardowego elementu <img>, aby wyświetlały się jak najszybciej.

Oczywiście nie zawsze jest tak jasne, kiedy witryny wyświetlają się na tylu ekranach o różnych rozmiarach. To, co znajduje się w części strony widocznej na ekranie laptopa, może znajdować się poniżej na urządzeniach mobilnych. Nie ma stuprocentowej rady, która pozwoli jak najlepiej poradzić sobie w każdej sytuacji. Musisz zrobić spis kluczowych zasobów strony i wczytywać je w typowy sposób.

Poza tym nie chcesz rygorystycznie podchodzić do części strony widocznej po przewinięciu, by nie przekraczać progu wyzwalania leniwego ładowania. Optymalnym rozwiązaniem może być utworzenie strefy buforowania w pewnej odległości od części strony widocznej po przewinięciu, dzięki czemu obrazy będą się ładować na długo, zanim użytkownik przewinie je do widocznego obszaru. Na przykład interfejs Intersection Observer API pozwala określić właściwość rootMargin w obiekcieoptions podczas tworzenia nowej instancji IntersectionObserver. W efekcie buforuje element, który aktywuje leniwe ładowanie, zanim element znajdzie się w widocznym obszarze:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

Jeśli wartość parametru rootMargin wygląda podobnie do wartości określonych dla właściwości CSS margin, jest to właśnie dlatego. W takim przypadku dolny margines obserwowanego elementu (domyślnie widoczny w przeglądarce, ale za pomocą właściwości root można zmienić to ustawienie na konkretny element) zostanie poszerzone o 256 pikseli. Oznacza to, że funkcja wywołania zwrotnego zostanie wykonana, gdy element graficzny znajdzie się w odległości maksymalnie 256 pikseli od widocznego obszaru, a obraz zacznie się ładować, zanim użytkownik go zobaczy.

Aby uzyskać ten sam efekt w przeglądarkach, które nie obsługują obserwacji intersekcji, użyj kodu obsługi zdarzeń przewijania i dostosuj wartość getBoundingClientRect tak, aby uwzględnić bufor.

Przesunięcia układu i obiekty zastępcze

Leniwe ładowanie multimediów może powodować przesunięcia układu, jeśli nie są używane obiekty zastępcze. Zmiany te mogą być dezorientujące dla użytkowników i wyzwalać kosztowne operacje układu DOM, które zużywają zasoby systemowe i przyczyniają się do zacinania. Rozważ użycie przynajmniej symbolu zastępczego jednolitego koloru, który zajmuje te same wymiary co obraz docelowy, albo technik takich jak LQIP czy SQIP, które wskazują treść elementu multimedialnego przed jego wczytaniem.

W przypadku tagów <img> element src powinien początkowo wskazywać obiekt zastępczy, dopóki ten atrybut nie zostanie zaktualizowany o końcowy adres URL obrazu. Aby wskazać obraz zastępczy, użyj atrybutu poster w elemencie <video>. Dodatkowo użyj atrybutów width i height w tagach <img> i <video>. Dzięki temu przejście z obiektów zastępczych na ostateczne obrazy nie zmieni renderowanego rozmiaru elementu po wczytaniu multimediów.

Opóźnienia dekodowania obrazu

Wczytywanie dużych obrazów w języku JavaScript i upuszczenie ich w modelu DOM może powiązać wątek główny, przez co interfejs przez krótki czas nie odpowiada podczas dekodowania. Asynchroniczne dekodowanie obrazów za pomocą metody decode przed wstawieniem ich do modelu DOM może ograniczyć tego typu zagniecenia, ale uwaga: ta funkcja nie jest jeszcze dostępna wszędzie i sprawia, że funkcja leniwego ładowania jest bardziej skomplikowana. Jeśli chcesz go użyć, musisz to sprawdzić. Poniżej pokazujemy, jak można użyć elementu Image.decode() z wartością zastępczą:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

Kliknij ten link CodePen, aby zobaczyć kod podobny do tego przykładu w działaniu. Jeśli większość obrazów jest dość mała, może to niewiele sprawdzić, ale z pewnością uda się ograniczyć zacięcie podczas leniwego ładowania dużych obrazów i wstawienia ich do DOM.

Gdy coś się nie wczytuje

Czasami zasoby multimedialne nie ładują się z jednego czy innego powodu i czasem występują błędy. Kiedy może to nastąpić? To zależy, ale mamy tu jeden hipotetyczny scenariusz: masz ustawioną zasadę buforowania HTML przez krótki czas (np. 5 minut), a użytkownik odwiedza witrynę lub przez dłuższy czas (np. kilka godzin) pozostawia nieaktualną kartę i wraca, aby przeczytać treść. W pewnym momencie procesu następuje ponowne wdrożenie. Podczas tego wdrożenia nazwa zasobu obrazu zmienia się w związku z obsługą wersji opartą na haszach lub jest całkowicie usuwana. Gdy użytkownik leniwie ładuje obraz, zasób jest niedostępny, a tym samym kończy się jego niepowodzeniem.

Są to stosunkowo rzadkie przypadki, ale w razie niepowodzenia leniwego ładowania może być potrzebny plan tworzenia kopii zapasowych. W przypadku obrazów rozwiązanie to może wyglądać np. tak:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

To, co zrobisz w przypadku błędu, zależy od Twojej aplikacji. Możesz na przykład zastąpić obszar zastępczy obrazu przyciskiem, który umożliwia użytkownikowi ponowną próbę wczytania obrazu, lub po prostu wyświetlić komunikat o błędzie w obszarze zastępczym obrazu.

Mogą też wystąpić inne scenariusze. Niezależnie od tego, co robisz, nie jest złym pomysłem sygnalizowanie użytkownikowi, że wystąpił błąd, i ewentualne podjęcie działań, jeśli coś pójdzie nie tak.

Dostępność JavaScript

Nie zakładaj, że JavaScript jest zawsze dostępny. Jeśli zamierzasz leniwie ładować obrazy, rozważ oferowanie znaczników <noscript>, które będą wyświetlać obrazy w sytuacji, gdy JavaScript będzie niedostępny. Najprostszy przykład kreacji zastępczej polega na użyciu elementów <noscript> do wyświetlania obrazów, gdy JavaScript jest wyłączony:

Jestem obrazem!

Jeśli JavaScript jest wyłączony, użytkownicy zobaczą zarówno obraz zastępczy, jak i obraz zawarty w elementach <noscript>. Aby obejść ten problem, umieść w tagu <html> klasę no-js w następujący sposób:

<html class="no-js">

Następnie umieść jeden wiersz wbudowanego skryptu w kodzie <head>, zanim jakiekolwiek żądania arkuszy stylów zostaną wysłane przez tagi <link>, które usuwają klasę no-js z elementu <html>, jeśli włączona jest obsługa JavaScriptu:

<script>document.documentElement.classList.remove("no-js");</script>

Na koniec użyj CSS, aby ukryć elementy z klasą leniwe, gdy JavaScript jest niedostępny:

.no-js .lazy {
  display: none;
}

Nie blokuje to wczytywania obrazów zastępczych, ale wynik jest bardziej pożądany. Osoby z wyłączonym JavaScriptem mają coś więcej niż obrazy zastępcze, co jest lepsze od zmiennych zastępczych i bez żadnych istotnych obrazów.