Les coupables de la dégradation des performances des applications web et la nécessité du Lazy Loading

Dans les applications web modernes, les images sont l'un des éléments clés qui influencent l'expérience utilisateur (UX) et les performances du web. Avec l'augmentation de l'utilisation d'images haute résolution et une proportion croissante de contenu d'image dans les pages, la lenteur du chargement des sites web est devenue un problème inévitable. Charger toutes les images en même temps lors du chargement initial entraîne des goulets d'étranglement de performance critiques comme suit :

  • Augmentation du temps de chargement initial (retards FCP, LCP) : Le téléchargement d'images en dehors du viewport retarde considérablement le moment où l'utilisateur peut réellement voir le contenu (First Contentful Paint) et le moment où l'élément de contenu le plus important est chargé (Largest Contentful Paint). Cela a un impact négatif sur les indicateurs Core Web Vitals.

  • Consommation de bande passante inutile : Les utilisateurs gaspillent des ressources réseau sur des images qu'ils ne verront pas en faisant défiler, ce qui accroît la charge des données en environnement mobile et perturbe le chargement d'autres ressources importantes dans les environnements desktop.

  • Augmentation de la charge serveur : Toutes les requêtes d'images se produisent simultanément, ce qui impose une charge excessive au serveur, entraînant une baisse de la stabilité du service.

Une des stratégies les plus efficaces pour résoudre ces problèmes est le lazy loading d'images. Le Lazy Loading est une technique où, au lieu de charger toutes les images au moment du chargement initial de la page, les images sont chargées de manière asynchrone quand elles entrent dans le viewport de l'utilisateur ou lorsqu'un seuil est atteint.

Principe de fonctionnement et méthode d'application du Lazy Loading des images

Les principales méthodes d'application du Lazy Loading se divisent en deux catégories, chacune ayant ses avantages et inconvénients, à choisir selon les besoins du projet.

1. Lazy Loading Native du navigateur (loading="lazy")

La méthode la plus simple et la plus puissante consiste simplement à ajouter l'attribut loading="lazy" à la balise HTML <img>. La plupart des navigateurs modernes la supportent et gèrent le Lazy Loading de manière optimisée.

<img src="placeholder.jpg"
     data-src="actual-image.jpg"
     alt="Description de l'image"
     loading="lazy"
     width="500"
     height="300">
  • Avantages :

    • Facilité d'implémentation : Peut être appliqué en ajoutant simplement un attribut HTML, sans code JavaScript supplémentaire.

    • Optimisation des performances : Étant implémenté au niveau du moteur du navigateur, il peut fonctionner plus efficacement et rapidement que des solutions basées sur JavaScript.

    • Moins de charge pour les développeurs : Géré automatiquement par le navigateur sans nécessiter de gestion complexe de l'API Intersection Observer ou des écouteurs d'événements.

  • Inconvénients :

    • Support du navigateur : Peut ne pas être pris en charge parfaitement par tous les navigateurs anciens. (Cependant, la plupart des navigateurs modernes prennent en charge cette fonctionnalité)

    • Manque de contrôle précis : Les développeurs n'ont pas la possibilité de contrôler minutieusement les seuils de chargement ou les stratégies de chargement.

Recommandations : Si aucune exigence de contrôle spécifique n'est nécessaire, il est recommandé d'appliquer en priorité le Lazy Loading Natif.

2. Lazy Loading basé sur JavaScript (utilisation de l'API Intersection Observer)

Le Lazy Loading basé sur JavaScript peut être implémenté lorsque des contrôles plus précis sont nécessaires ou si un fallback est requis pour les navigateurs qui ne supportent pas l'attribut loading="lazy". Par le passé, la méthode consistait principalement à utiliser un écouteur d'événements de défilement, mais en raison de problèmes de performance, l'utilisation de l'API Intersection Observer est devenue la norme.

Logique d'implémentation basique :

  1. Initialement, spécifiez une image de remplacement dans l'attribut src, ou stockez l'URL de l'image réelle dans l'attribut data-src.

  2. Créez une instance de l'Intersection Observer et observez les éléments d'image.

  3. Lorsque l'élément image entre dans le viewport ou atteint un seuil défini, une fonction de rappel de l'Observateur est exécutée.

  4. Dans la fonction de rappel, déplacez l'URL de l'image réelle stockée dans data-src vers l'attribut src pour charger l'image.

  5. Lorsqu le chargement de l'image est terminé, arrêtez d'observer l'élément image.

// HTML (exemple)
// <img class="lazyload" data-src="actual-image.jpg" alt="Description">

// JavaScript
document.addEventListener("DOMContentLoaded", function() {
    const lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));

    if ("IntersectionObserver" in window) {
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    let lazyImage = entry.target;
                    lazyImage.src = lazyImage.dataset.src;
                    // lazyImage.srcset = lazyImage.dataset.srcset; // srcset également si nécessaire
                    lazyImage.classList.remove("lazyload");
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        }, {
            // Root Margin: zone à charger les images à proximité des bords du viewport (px ou %)
            // Exemple : "0px 0px 200px 0px" charge à l'avance 200px en bas
            rootMargin: "0px 0px 200px 0px"
        });

        lazyImages.forEach(function(lazyImage) {
            lazyImageObserver.observe(lazyImage);
        });
    } else {
        // Fallback pour les navigateurs qui ne supportent pas l'IntersectionObserver
        // (ex : utilisation d'un écouteur d'événements de défilement ou chargement immédiat de toutes les images)
        // Cette partie n'est pas recommandée pour des raisons de performance, il est donc conseillé de le juger en fonction des navigateurs cibles principaux.
        lazyImages.forEach(function(lazyImage) {
            lazyImage.src = lazyImage.dataset.src;
        });
    }
});
  • Bibliothèques JavaScript Lazy Loading populaires :

    • lazysizes : très légère, conviviale pour le SEO, et prend en charge divers formats d'images y compris srcset et picture. Peut également être utilisée comme fallback pour le Lazy Loading natif.

    • lozad.js : célèbre pour sa petite taille et ses performances élevées.

Considérations et conseils d'optimisation lors de l'application du Lazy Loading

Bien que le Lazy Loading soit une technique puissante d'optimisation des performances, son application indiscriminée à toutes les images peut nuire à l'expérience utilisateur.

  1. Gestion des images 'Above the Fold' : Les images visibles immédiatement dans l'écran (Above the Fold) ne devraient pas avoir le Lazy Loading appliqué. Ces images ont un impact direct sur le Largest Contentful Paint (LCP) de la page, donc il faut spécifier loading="eager" ou les exclure du Lazy Loading pour qu'elles se chargent immédiatement.
<img src="logo.png" alt="Logo de l'entreprise" width="100" height="50" loading="eager">

Analyse de la répartition du LCP

  1. Spécifier les attributs width et height : Indiquez les attributs width et height de l'image dans le code HTML afin de prévenir le changement de mise en page cumulatif (Cumulative Layout Shift, CLS). Cela permet au navigateur de réserver l'espace pour l'image avant qu'elle ne soit chargée, empêchant ainsi que la mise en page ne saute pendant le chargement. Cela est essentiel pour améliorer les scores des Core Web Vitals.

  2. Utiliser des images de remplacement : Pour éviter un espace visuel vide pour l'utilisateur jusqu'à ce que l'image Lazy Loading soit chargée, il est judicieux d'utiliser une image floue à basse résolution ou un fond de couleur unie comme image de remplacement. Cela informe l'utilisateur que la page est en cours de chargement.

  3. Utiliser srcset et <picture> : Combinez les images réactives (srcset) et l'art direction (picture tag) avec le Lazy Loading pour fournir des images optimisées pour différentes tailles d'écran et résolutions, réduisant encore les téléchargements d'images inutiles.

<picture>
    <source srcset="image-large.webp" type="image/webp" media="(min-width: 1200px)" loading="lazy">
    <source srcset="image-medium.webp" type="image/webp" media="(min-width: 768px)" loading="lazy">
    <img src="placeholder.jpg" data-src="image-small.jpg" alt="Description" loading="lazy">
</picture>

Score de performance Lighthouse

Conclusion : Capturer deux oiseaux avec une pierre avec le Lazy Loading

Le Lazy Loading des images ne se limite pas à retarder le chargement des images, il s'agit d'une technique d'optimisation web indispensable qui améliore de manière significative les performances de chargement initial de la page, optimise l'utilisation de la bande passante et réduit la charge serveur. Avec l'augmentation de l'importance des indicateurs de performance web tels que les Core Web Vitals, l'application stratégique du Lazy Loading doit être une priorité pour les développeurs.

Privilégiez le Lazy Loading natif, mais lorsque des contrôles précis sont nécessaires ou lorsque vous devez supporter des environnements de navigateur spécifiques, envisagez une implémentation JavaScript basée sur l'API Intersection Observer ou l'utilisation de bibliothèques. En outre, appliquez également les conseils d'optimisation tels que la gestion des images 'Above the Fold', la spécification des attributs width/height et l'utilisation d'images de remplacement pour offrir aux utilisateurs une expérience web rapide et agréable.