Prérequis
- Niveau : bases HTML, notion JavaScript pour la version Intersection Observer.
- Outils : VS Code + Live Server, navigateur moderne (DevTools onglet Network + Lighthouse).
- Temps estimé : 45 min.
Pourquoi le lazy loading ?
Sur une page typique, les images représentent 60 à 80 % du poids total. Charger uniquement ce qui est visible accélère le premier rendu, économise la bande passante mobile, et améliore directement votre score Core Web Vitals (LCP, INP, CLS) — donc votre référencement Google.
Le lazy loading : charger les images uniquement quand elles sont visibles
Sur une page web typique, 60% du poids total vient des images. Si votre page contient 20 images et que l’utilisateur n’en voit que 3 sans scroller, vous gaspillez de la bande passante et ralentissez le chargement pour rien. Le lazy loading résout ce problème : les images ne se chargent que lorsqu’elles entrent dans le viewport (la zone visible de l’écran). Au Sénégal, où les connexions mobiles sont souvent limitées, cette technique peut réduire le temps de chargement initial de 50% ou plus.
Méthode 1 : l’attribut loading= »lazy » (la plus simple)
Depuis 2020, tous les navigateurs modernes supportent le lazy loading natif avec un simple attribut HTML :
<!-- ✅ Lazy loading natif — rien d'autre à faire -->
<img src="photo-équipe.jpg"
alt="Notre équipe à Dakar"
width="800"
height="450"
loading="lazy">
<!-- ❌ Ne PAS mettre loading="lazy" sur les images au-dessus de la ligne de flottaison -->
<img src="hero-banner.jpg"
alt="Bannière principale"
width="1200"
height="600"
loading="eager"> <!-- ou simplement omettre l'attribut -->
Règles :
- Ajoutez
loading="lazy"à toutes les images SAUF celles visibles immédiatement (hero, logo, images au-dessus du premier écran) - Spécifiez TOUJOURS
widthetheight— cela évite le « layout shift » (le saut de page quand l’image se charge) - Supporté par 95%+ des navigateurs en 2026 (informations vérifiées en avril 2026, susceptibles d’évoluer)
Méthode 2 : Intersection Observer (contrôle total)
Pour un contrôle avancé (effet de fade-in, placeholder, etc.), utilisez l’API Intersection Observer :
<!-- HTML : src contient un placeholder, data-src contient la vraie image -->
<img class="lazy"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 450'%3E%3Crect fill='%23f0f0f0' width='800' height='450'/%3E%3C/svg%3E"
data-src="photo-équipe.jpg"
alt="Notre équipe"
width="800"
height="450">
// JavaScript
const lazyImages = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // Charger la vraie image
img.classList.add('loaded'); // Ajouter la classe pour l'animation
img.classList.remove('lazy');
observer.unobserve(img); // Arrêter d'observer cette image
}
});
}, {
rootMargin: '200px' // Commencer le chargement 200px avant que l'image soit visible
});
lazyImages.forEach(img => observer.observe(img));
/* CSS : effet de fade-in */
img.lazy {
opacity: 0;
transition: opacity 0.5s ease;
}
img.loaded {
opacity: 1;
}
rootMargin: '200px' commence le chargement 200px avant que l’image n’entre dans le viewport. L’utilisateur ne voit jamais un espace vide car l’image est déjà chargée quand il scrolle jusqu’à elle.
Lazy loading des images de fond CSS
L’attribut loading="lazy" ne fonctionne que sur les balises <img>. Pour les images de fond CSS, utilisez Intersection Observer :
<div class="hero-section lazy-bg" data-bg="hero-background.jpg">
<h1>Bienvenue</h1>
</div>
const lazyBgs = document.querySelectorAll('.lazy-bg');
const bgObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.backgroundImage = 'url(' + entry.target.dataset.bg + ')';
bgObserver.unobserve(entry.target);
}
});
});
lazyBgs.forEach(el => bgObserver.observe(el));
Images responsive avec srcset et lazy loading
Combinez le lazy loading avec srcset pour servir la taille d’image adaptée à l’écran :
<img srcset="photo-400w.jpg 400w,
photo-800w.jpg 800w,
photo-1200w.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 900px) 800px,
1200px"
src="photo-800w.jpg"
alt="Description de la photo"
loading="lazy"
width="800"
height="450">
Le navigateur choisit automatiquement la taille optimale. Sur un téléphone à Dakar en 3G, il charge la version 400px (30 Ko) au lieu de la version 1200px (200 Ko).
Le format WebP : 30% plus léger que JPEG
Utilisez la balise <picture> pour servir du WebP aux navigateurs compatibles et du JPEG comme fallback :
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Description" loading="lazy" width="800" height="450">
</picture>
Convertissez vos images en WebP avec Squoosh (en ligne, gratuit) ou en ligne de commande avec cwebp photo.jpg -o photo.webp -q 80.
Mesurer l’impact du lazy loading
- Ouvrez DevTools → Network → filtrez par « Img »
- Rechargez la page (Ctrl+Shift+R) et notez le nombre d’images chargées
- Scrollez progressivement et observez les nouvelles images qui se chargent
- Comparez le temps de chargement initial avec et sans lazy loading
- Testez avec PageSpeed Insights pour voir l’impact sur les Core Web Vitals
Erreurs fréquentes
loading="lazy" sur l’image hero (LCP)
Cause : on lazy-load la première image visible → le LCP (Largest Contentful Paint) s’effondre.
Solution : loading="eager" + fetchpriority="high" sur l’image hero. lazy uniquement sous la ligne de flottaison.
Layout shift (CLS) à cause d’images sans dimensions
Cause : width et height oubliés → la page « saute » quand l’image arrive.
Solution : précisez TOUJOURS width et height (le navigateur calcule l’aspect-ratio).
Lazy loading natif sur image background CSS
Cause : loading="lazy" ne fonctionne PAS sur background-image.
Solution : utilisez Intersection Observer pour appliquer la background-image au moment voulu, ou préférez une vraie balise <img>.
WebP servi sans fallback
Cause : WebP n’est pas supporté par les très vieux Safari (< 14) ou IE.
Solution : en 2026 c’est universel, mais la balise <picture> avec <source type="image/webp"> + <img> JPEG reste le pattern le plus sûr.
Exercice
Créez une page galerie avec 20 images (utilisez picsum.photos pour des images de test) :
- Appliquez
loading="lazy"à toutes les images sauf les 3 premières - Ajoutez un effet de fade-in avec Intersection Observer
- Utilisez srcset pour 3 tailles différentes
- Mesurez la différence de poids dans l’onglet Network
Pour creuser ce sujet
- Optimiser les performances
- Galerie photo responsive avec CSS Grid
- Référence : MDN — img loading
- Compression : Squoosh (Google, gratuit)
- Core Web Vitals : web.dev — Vitals
Étape 1 — Comprendre pourquoi le lazy loading est critique en Afrique de l’Ouest
La latence moyenne d’une connexion 4G à Dakar, Abidjan ou Cotonou oscille entre 70 et 200 ms, avec des pics au-delà de 500 ms en heure de pointe. Charger 30 images d’un coup au chargement initial d’une page fait exploser le LCP (Largest Contentful Paint) à 6 ou 7 secondes, alors que Google considère acceptable tout ce qui reste sous 2,5 secondes. Le lazy loading consiste à ne télécharger que les images visibles immédiatement, et à charger les autres au fur et à mesure du défilement.
Avant de coder, mesurez la situation actuelle. Ouvrez Chrome DevTools (F12), onglet Network, cochez « Disable cache », simulez un réseau « Slow 4G », rechargez la page. Notez le poids total et le temps jusqu’à l’événement load. Sur un site WordPress non optimisé, on observe couramment 4 Mo et 11 secondes. Cette mesure de référence vous servira à valider les progrès après chaque étape.
Étape 2 — Activer le lazy loading natif HTML (loading= »lazy »)
Depuis 2020, tous les navigateurs majeurs (Chrome, Edge, Firefox, Safari 15.4+) supportent l’attribut HTML natif loading="lazy". C’est le plus simple, le plus performant, et il fonctionne sans JavaScript. Ajoutez l’attribut sur chaque balise image, sauf la première (LCP), pour laquelle vous voulez au contraire forcer le chargement immédiat avec loading="eager" et fetchpriority="high".
<!-- Image héro (au-dessus du pli) -->
<img src="/uploads/hero-dakar.jpg"
alt="Vue de Dakar au coucher du soleil"
width="1200" height="600"
loading="eager" fetchpriority="high">
<!-- Images suivantes (sous le pli) -->
<img src="/uploads/photo-2.jpg"
alt="Salle de formation"
width="800" height="500"
loading="lazy" decoding="async">
Les attributs width et height sont obligatoires : sans eux, le navigateur ne peut pas réserver l’espace et provoque un CLS (Cumulative Layout Shift) catastrophique. Rechargez la page après modification : le panel Network doit montrer une chute du nombre d’images téléchargées initialement.
Étape 3 — Définir une stratégie pour les images au-dessus du pli
L’erreur la plus fréquente est de mettre loading="lazy" sur l’image principale. Conséquence : le navigateur attend la fin du parsing CSS pour décider de la charger, et le LCP se dégrade. Identifiez systématiquement la ou les images visibles sans scroll sur mobile (320×568 minimum) et excluez-les du lazy loading.
Sur WordPress, ce comportement est automatisé depuis la version 5.9 : le cœur applique loading="lazy" à toutes les images sauf la première. Vérifiez en inspectant le HTML rendu. Si votre thème utilise des images de fond CSS pour le héro, ajoutez un <link rel="preload" as="image"> dans le <head> pour forcer le téléchargement précoce.
Étape 4 — Servir les images en WebP ou AVIF avec srcset
Le lazy loading ne suffit pas si vos images pèsent 800 Ko en JPEG. Convertissez systématiquement vers WebP (qualité 80, gain typique 30 %) ou AVIF (gain 50 % mais encodage plus lent). Utilisez la balise <picture> pour offrir plusieurs formats et plusieurs résolutions, le navigateur choisit le plus léger qu’il sait décoder.
<picture>
<source type="image/avif"
srcset="/img/dakar-480.avif 480w, /img/dakar-1200.avif 1200w"
sizes="(max-width: 768px) 100vw, 1200px">
<source type="image/webp"
srcset="/img/dakar-480.webp 480w, /img/dakar-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 1200px">
<img src="/img/dakar-1200.jpg" alt="Marché Sandaga"
width="1200" height="800" loading="lazy" decoding="async">
</picture>
Pour automatiser la conversion sur un projet existant, utilisez cwebp (libwebp) en ligne de commande ou un script Node avec sharp. Sur WordPress, le plugin gratuit Modern Image Formats fait le travail à l’upload.
Étape 5 — Implémenter IntersectionObserver pour les cas avancés
Pour des composants complexes (carrousel, grille de cartes lazy, vidéos d’arrière-plan), l’attribut HTML natif ne suffit pas. L’API IntersectionObserver permet de détecter quand un élément entre dans le viewport et de déclencher un comportement personnalisé : charger une image, lancer une animation, démarrer une vidéo.
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) img.srcset = img.dataset.srcset;
io.unobserve(img);
});
}, { rootMargin: "200px 0px", threshold: 0.01 });
document.querySelectorAll('img[data-src]').forEach(img => io.observe(img));
L’option rootMargin: "200px 0px" déclenche le chargement 200 pixels avant que l’image n’entre dans le viewport, ce qui évite l’effet de « pop-in » brutal sur scroll rapide. Testez sur un téléphone Android d’entrée de gamme ; si l’image apparaît terminée quand l’utilisateur la voit, c’est un succès.
Étape 6 — Lazy loader les iframes (YouTube, Maps, embeds)
Une iframe YouTube non lazy charge ~600 Ko de JavaScript et 8 requêtes vers Google avant même que l’utilisateur ait cliqué sur play. Multipliez par 3 ou 4 vidéos par page et la note 4G devient salée pour vos visiteurs au Sénégal ou au Bénin. La parade est simple : ajouter loading="lazy" à toutes les iframes non visibles.
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
title="Tutoriel pratique"
width="560" height="315"
loading="lazy"
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
Lectures complémentaires, remplacez l’iframe par une miniature cliquable (composant lite-youtube-embed). Le poids initial tombe alors à 3 Ko, et la vidéo ne se charge réellement que sur clic. Gain typique : -2 secondes sur le LCP d’une page contenant trois vidéos.
Étape 7 — Mesurer l’impact avec Lighthouse et WebPageTest
Une optimisation non mesurée n’existe pas. Après chaque modification, lancez Lighthouse en mode mobile sur Chrome DevTools (onglet Lighthouse, catégorie Performance, throttling Slow 4G). Notez les scores LCP, CLS, INP avant/après. Pour une mesure réaliste, complétez avec WebPageTest configuré sur un nœud Casablanca ou Le Cap, et un profil de connexion « 3GFast ».
Vos cibles pratiques : LCP < 2,5 s, CLS < 0,1, INP < 200 ms, taille totale < 1,5 Mo. Un site WordPress correctement lazy-loadé passe typiquement de 4 Mo à 800 Ko sur le chargement initial, et de 11 secondes à 3 secondes sur Slow 4G. Validation pratique est aussi visible dans Google Search Console (Core Web Vitals) deux à trois semaines après la mise en production.
Étape 8 — Pièges fréquents et comment les éviter
Cinq erreurs reviennent constamment dans les audits. Première : appliquer lazy à toutes les images sans exception, ce qui dégrade le LCP. Deuxième : oublier width et height, ce qui détruit le CLS. Troisième : utiliser des bibliothèques JavaScript lourdes (lazysizes 18 Ko) alors que le natif suffit dans 95 % des cas en 2026. Quatrième : ne pas tester sur connexion lente réelle, seulement en wifi de bureau. Cinquième : oublier les images en CSS (backgrounds), qui ne bénéficient pas du lazy natif et nécessitent IntersectionObserver.
Maintenez une checklist par projet : audit Lighthouse mensuel, conversion WebP/AVIF systématique à l’upload, balisage <picture> pour les héros, test Slow 4G obligatoire avant déploiement. Pour explorer plus loin, lisez nos guides optimiser les Core Web Vitals sur WordPress et convertir vos images en WebP et AVIF en batch.
Étape 9 — Automatiser le lazy loading sur un site WordPress existant
Sur un site WordPress de plusieurs centaines d’articles, vous n’allez pas modifier chaque image à la main. Trois leviers à activer dans cet ordre. D’abord, vérifiez que le cœur WordPress 6.x applique bien loading="lazy" par défaut (inspectez la source d’un article au hasard). Ensuite, installez un plugin de conversion à la volée (Modern Image Formats, ShortPixel, EWWW Image Optimizer) qui produit des dérivés WebP/AVIF et réécrit les balises <img> en <picture>. Enfin, activez un cache de pages (LiteSpeed Cache, WP Rocket) qui sert le HTML pré-rendu en moins de 200 ms.
Sur les anciens articles importés depuis un autre CMS, lancez une recherche-remplacement contrôlée pour ajouter loading="lazy" decoding="async" aux balises <img> qui en sont dépourvues. Le plugin Better Search Replace fait cela proprement avec un dry-run obligatoire avant écriture. Sauvegardez la base de données complète avant l’opération. Le bon résultat se reconnaît à : un nouveau passage Lighthouse mobile sur cinq articles représentatifs, scores Performance moyens supérieurs à 85, sans régression visible côté éditorial.