Le meilleur truc pour diminuer et optimiser le chargement d’une page, c’est de différer le chargement des grosses images d’une page Web! C’est-à-dire d’attendre que l’image soit dans l’écran avant de la télécharger. Des grosses images, car trop c’est comme pas assez! Exagérer sur le nombre d’images gérées en JavaScript peut tout aussi alourdir le chargement initial de la page...
Les images sont de loin ce qui est le plus lourd dans une page Web et il n’est pas rare d’en rencontrer beaucoup dans une seule page Web. Ce qui ralentit considérablement le chargement de la page, particulièrement avec un téléphone intelligent. C’est aussi ce qui donne la plus grande impression dans une page Web. Avant même d’évaluer le contenu, l’internaute moyen est influencé par la qualité des images. Malgré l’intelligence ou l’adage, l’habit fait malheureusement toujours le moine lorsqu’il est question de la première impression! Le pire, c’est que ces images se retrouvent généralement à l’extérieure de l’écran. C’est donc inutile de les charger avant qu’elles apparaissent!
Au-delà du format d’image, d’une bonne compression ou des images adaptatives, l’idée est de ne pas charger du tout les images et d’attendre après le défilement de la page! En prime, vous obtiendrez un bien meilleur résultat dans le test « Google PageSpeed Insights »!
L’attribut « loading »
Le plus simple est d’utiliser l’attribut native loading
, de plus en plus compatible directement dans l’élément <img>
.
<img src="image.jpg" alt="Texte alternatif" loading="lazy" />
Et ça fonctionne. Si l’image n’est pas déjà dans la mémoire cache, elle se charge seulement lorsque l’internaute fait défiler la page jusqu’à son emplacement. C’est très facile de tester cette fonctionnalité. Ouvrez la console du navigateur (la touche [F12]). Vous remarquerez qu’il n’y a aucune requête pour charger l’image. Si c’est le cas, c’est que votre navigateur n’est pas compatible ou que l’image est déjà dans la mémoire cache. Pour s’en assurer, ajouter un paramètre au bout de l’adresse de l’image. Exemple: image.jpg?v-1.0.
Son avantage, d’une simplicité infantile. Son désavantage, pas facile d’ajouter une animation lors du chargement.
La « Politique de fonctionnalité » (Feature policy), un peu comme la « Politiques de sécurité du contenu » (Content Security Policies) permet aux développeurs d’activer ou désactivé cette fonctionnalité pour tous les éléments image et iframe de la page. Il semblerait que Google ait malheureusement retiré cette fonctionnalité!
Avec JavaScript
L’avantage avec la méthode JavaScript c’est qu’on peut profiter du chargement différé pour changer et animer l’image, comme un effet de fondu. Pour ma part j’utilise souvent l’excellent [be]Lazy.js par Bjørn Klinggaard (Télécharger la version v1.8.2). Plus simplement, voilà un script de la W3C :
La technique consiste à détecter un des évènements (défilement, changement d’orientation ou changement de dimension de la page) pour appeler la fonction qui ajoute un "timer" pour permettre en temps réel de tester la position de chaque image (avec la classe « lazy ») par rapport à la position de la page (innerHeight+pageYOffset). Si la position de l’image est plus petite que la position de la page, on utilise l’attribut data-src
pour définir le src
(URL) de l’image tout en supprimant la classe « lazy » (qui ne sert à rien d’autre) question d’éviter de traiter une deuxième fois l’image. Puis on supprime les écouteurs d’évènement.
<!DOCTYPE html> <html> <head> <title>Exemple de la W3C</title> <style> img { width: 500px; height: 350px; display: block; margin: 10px auto; } </style> </head> <body> <img src="https://images.unsplash.com/photo-1482784160316-6eb046863ece?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img src="https://images.unsplash.com/photo-1488441770602-aed21fc49bd5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1050&q=80" /> <!-- Par défaut, aucune des images suivante n’est affichée par défaut avec l’attribut « data-src » --> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1545105511-839f4a45a030?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1491250974528-9443b2902f18?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1500993855538-c6a99f437aa7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" /> <img class="lazy-load" data-src="https://images.unsplash.com/photo-1501018313157-78d10f597133?ixlib=rb-1.2.1&auto=format&fit=crop&w=1055&q=80" /> <!-- Personnellement, j'aime utiliser une image par défaut encodée avec les bonne dimensions --> <img class="lazy-load" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" width="200" height="200" data-src="image.jpg" /> <script> document.addEventListener("DOMContentLoaded", function() { let lazyloadImages = document.querySelectorAll("img.lazy-load"); let lazyloadThrottleTimeout; function lazyload() { if(lazyloadThrottleTimeout) { clearTimeout(lazyloadThrottleTimeout); } lazyloadThrottleTimeout = setTimeout(function() { let scrollTop = window.pageYOffset; lazyloadImages.forEach(function(img) { if(img.offsetTop < (window.innerHeight + scrollTop)) { img.src = img.dataset.src; img.classList.remove("lazy"); } }); if(lazyloadImages.length == 0) { document.removeEventListener("scroll", lazyload); window.removeEventListener("resize", lazyload); window.removeEventListener("orientationChange", lazyload); } }, 20); } document.addEventListener("scroll", lazyload); window.addEventListener("resize", lazyload); window.addEventListener("orientationChange", lazyload); }); </script> </body> </html>
Source : « Lazy load images with JavaScript » - W3C
Chargement différé avec l’API « IntersectionObserver »
L’écouteur ou l’observateur d’intersection observer de manière asynchrone l’évolution de l’intersection d’un élément cible avec un élément ancêtre ou avec la zone d’affichage d’un document de niveau supérieur. Il permet de savoir si un élément de la page s’en vient, s’il est arrivé ou s’il est parti sans devoir utiliser un « timer ». Un peu comme un train dans une gare. Il a besoin de deux paramètres, l’appel de fonction (callback) et les options :
- root - La racine. L’élément ancêtre ou la fenêtre d’affichage que l’élément observé va croiser (la gare).
- rootMargin - La racine, mais avec une plus grande zone pour surveiller les intersections.
- threshold - Comme l’option précédente, mais avec un tableau de pourcentage entre 0 et 1,0.
var options = { root: document.querySelector("lagare"), rootMargin: "0px", threshold: 1.0 } var observateur = new IntersectionObserver(maFonction, options);
Voilà un code rudimentaire qui définie les option (par défaut) et crée une instance de l’API :
<style> /* Optionnel : permet d'ajouter une animation avec la transparence */ img.lazy-image { opacity: 0.1; will-change: opacity; transition: all 0.3s; } img.lazy-image.chergee { opacity: 1; } </style> <img id="oImage" class="lazy-image" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" width="200" height="200" data-src="image.jpg" alt="Image" /> <script> var options = { root: null, rootMargin: "0px", threshold: 1.0 } var maFonctionIntersection = function(entrees, observateur) { // Boucle chaque élément de la cible entrees.forEach(entree => { // Trace qui affiche la valeur « isIntersecting » console.log("En intersection : "+ entree.isIntersecting); // Si l'élément est en intersection avec la cible // lui donner l'adresse (src) de l'attribut « data-src » if(entree.isIntersecting) entree.target.src = entree.target.dataset.src; // Optionnel : quand l’image est chargée, ajouter la classe « chergee » pour appliquer l’animation entree.target.onload = () => entree.target.classList.add("chergee"); }); }; // Assure que la page est chargée window.addEventListener("load", function(event) { // Crée l'instance de l'observateur d'intersection var observateur = new IntersectionObserver(maFonctionIntersection, options); // Crée l'instance de l'image var oImage = document.querySelector("#oImage"); // Transmets la cible à l'observateur observateur.observe(oImage); }); </script>
Il n’y a qu’Internet Explorer qui n’est pas compatible avec L’API IntersectionObserver. Pour plus de détail sur l’API, consultez le texte de « Intersection Observer API. Rahul Nanwani (CSS-Tricks) offre aussi un exemple plus efficace avec l’écouteur « IntersectionObserver ».
Image de fond (background-image)
Ce qui est intéressant avec l’API « IntersectionObserver » c’est qu’il permet aussi de différer le chargement des images de fond, chose impossible avec l’attribut loading="lazy"
. L’exemple suivant permet aussi de détecter quand le défilement de la page arrive à l’endroit où le conteneur de l’image de fond se situe à l’aide de l’écouteur « IntersectionObserver » pour définir le « url » de l’image de fond. Cette fois en interchangeant la classe CSS « lazy » :
<style> #bg-image.lazy { background-image: none; background-color: #F1F1FA; } #bg-image { background-image: url("image.jpg"); max-width: 600px; height: 400px; } </style> <div id="bg-image" class="lazy"></div> <script> document.addEventListener("DOMContentLoaded", function() { var lazyloadImages; if ("IntersectionObserver" in window) { lazyloadImages = document.querySelectorAll(".lazy"); var imageObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { var image = entry.target; image.classList.remove("lazy"); imageObserver.unobserve(image); } }); }); lazyloadImages.forEach(function(image) { imageObserver.observe(image); }); } else { var lazyloadThrottleTimeout; lazyloadImages = document.querySelectorAll(".lazy"); function lazyload () { if(lazyloadThrottleTimeout) { clearTimeout(lazyloadThrottleTimeout); } lazyloadThrottleTimeout = setTimeout(function() { var scrollTop = window.pageYOffset; lazyloadImages.forEach(function(img) { if(img.offsetTop < (window.innerHeight + scrollTop)) { img.src = img.dataset.src; img.classList.remove("lazy"); } }); if(lazyloadImages.length == 0) { document.removeEventListener("scroll", lazyload); window.removeEventListener("resize", lazyload); window.removeEventListener("orientationChange", lazyload); } }, 20); } document.addEventListener("scroll", lazyload); window.addEventListener("resize", lazyload); window.addEventListener("orientationChange", lazyload); } }) </script>
Source : « The Complete Guide to Lazy Loading Images »
Conclusion
Dans la mesure ou l’attributloading="lazy"
est de plus en plus supporté, ces scripts deviennent lourd et inutile. Sinon, comme je disait plus haut, j’utilise souvent l’excellent et très légé script [be]Lazy.js par Bjørn Klinggaard (Télécharger la version v1.8.2). Mais il existe plusieurs llibrairie gratuite :
- lazysizes (très complet)
- vanilla-lazyload (images, images de fond et videos)
- lozad.js
- yall.js (compatible IE11)
- react-lazyload (avec la librairie React)
Références
- Lazy load images with JavaScript - W3C
- The Complete Guide to Lazy Loading Images - Rahul Nanwani (CSS-Tricks)
- [be]Lazy.js par Bjørn Klinggaard.
- Le chargement différé - MDN Web Docs Store
- Google PageSpeed Insights
- Can I use Lazy loading via attribute for images & iframes