Lazy Loading
Loading=Lazy
Performance
Optimisation
Web Vitals
Images
Maîtrisez le lazy loading natif avec l'attribut loading. Optimisation performance, intersection observer, fallback. 11+ exemples et bonnes pratiques.
Pourquoi le lazy loading ?
Impact sur les performances :
- LCP amélioré : Largest Contentful Paint = temps avant que le contenu principal soit visible
- Moins de requêtes HTTP au chargement initial
- Économie de bande passante pour les utilisateurs qui ne scrollent pas
- Meilleure expérience mobile sur connexions 3G/4G lentes
Attention : Les images "lazy" doivent avoir des dimensions définies pour éviter les Cumulative Layout Shift (CLS). Le layout doit être stable.
L'attribut loading="lazy"
Utilisation basique
<!-- Image chargée avec lazy loading -->
<img
src="image.jpg"
alt="Description"
loading="lazy"
width="400"
height="300"
/>
Attributs importants :
loading="lazy": charge l'image quand elle approche du viewportloading="eager": charge immédiatement (par défaut)widthetheight: obligatoires pour éviter le CLS
Quand utiliser lazy loading
<!-- Images au-dessus du fold : loading="eager" ou sans attribut -->
<img
src="hero.jpg"
alt="Hero image"
loading="eager"
width="800"
height="400"
/>
<!-- Images au-dessous du fold : loading="lazy" -->
<img
src="gallery-1.jpg"
alt="Gallery image 1"
loading="lazy"
width="300"
height="300"
/>
<!-- Images loin en bas : loading="lazy" -->
<img
src="testimonial-image.jpg"
alt="Testimonial"
loading="lazy"
width="200"
height="200"
/>
Galerie d'images complète
<!-- Galerie avec lazy loading -->
<div class="gallery">
<img src="image-1.jpg" alt="Image 1" loading="lazy" width="300" height="300" class="gallery-item">
<img src="image-2.jpg" alt="Image 2" loading="lazy" width="300" height="300" class="gallery-item">
<img src="image-3.jpg" alt="Image 3" loading="lazy" width="300" height="300" class="gallery-item">
<img src="image-4.jpg" alt="Image 4" loading="lazy" width="300" height="300" class="gallery-item">
</div>
<style>
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.gallery-item {
width: 100%;
height: auto;
aspect-ratio: 1 / 1;
object-fit: cover;
}
</style>
Définir les dimensions (LCP et CLS)
Pourquoi les dimensions sont cruciales
Sans dimensions définies, le navigateur ne sait pas l'espace que l'image occupera. Cela cause des Cumulative Layout Shift (CLS) quand l'image charge.
<!-- ❌ Mauvais : pas de dimensions, CLS garanti -->
<img src="image.jpg" alt="Image" loading="lazy"/>
<!-- ✅ Bon : dimensions définies, pas de CLS -->
<img
src="image.jpg"
alt="Image"
loading="lazy"
width="400"
height="300"
/>
Utiliser aspect-ratio pour le responsive
/* Garder le ratio 16:9 même si la largeur change */
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
}
/* Anciennes navigateurs sans aspect-ratio */
img {
width: 100%;
height: auto;
}
Placeholder technique : LQIP
<!-- Placeholder bas-res pendant le chargement -->
<img
src="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
alt="Placeholder"
loading="lazy"
width="400"
height="300"
srcset="image-small.jpg 400w, image-medium.jpg 800w, image-large.jpg 1200w"
style="background-size: cover;"
/>
Intersection Observer pour plus de contrôle
Lazy loading personnalisé
<img
class="lazy"
data-src="image.jpg"
alt="Image"
width="400"
height="300"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E"
/>
JavaScript avec Intersection Observer
// Créer un Intersection Observer
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
// Écouter le chargement
img.onload = () => {
img.classList.add('loaded');
};
// Arrêter d'observer cet élément
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // Commencer à charger 50px avant d'entrer dans le viewport
});
// Observer toutes les images lazy
document.querySelectorAll('img.lazy').forEach(img => {
imageObserver.observe(img);
});
Avec fade-in animation
img {
transition: opacity 0.3s ease;
}
img.lazy {
opacity: 0;
}
img.loaded {
opacity: 1;
}
Lazy loading des iframes
loading="lazy" sur les iframes
<!-- Youtube, cartes, etc. avec lazy loading -->
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
width="560"
height="315"
title="Video"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
></iframe>
<!-- Google Maps avec lazy loading -->
<iframe
src="https://www.google.com/maps/embed?pb=..."
width="400"
height="300"
loading="lazy"
></iframe>
Cela économise énormément de bande passante, surtout pour les pages avec plusieurs vidéos.
Lazy loading + images responsives
Combiner loading="lazy" et srcset
<img
src="image-400w.jpg"
srcset="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px"
alt="Responsive image"
loading="lazy"
width="800"
height="600"
/>
Avec element picture
<picture>
<source
srcset="image-small.webp"
media="(max-width: 600px)"
type="image/webp"
/>
<source
srcset="image-large.webp"
media="(min-width: 601px)"
type="image/webp"
/>
<img
src="image-fallback.jpg"
alt="Image"
loading="lazy"
width="800"
height="600"
/>
</picture>
Fallback et polyfill
Détecter le support
// Vérifier si le navigateur supporte loading="lazy"
const supportsLazyLoading = 'loading' in HTMLImageElement.prototype;
if (!supportsLazyLoading) {
// Charger un polyfill ou utiliser Intersection Observer
loadPolyfill();
}
Polyfill avec Intersection Observer
// Pour les navigateurs n'ayant pas loading="lazy"
if (!('loading' in HTMLImageElement.prototype)) {
const images = document.querySelectorAll('img[loading="lazy"]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.src;
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
}
Suivi et analytics
Tracker les images lazy loading
// Tracker quand une image lazy est chargée
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
img.addEventListener('load', () => {
console.log(`Image chargée : ${img.alt}`);
// Envoyer à Google Analytics, etc.
if (window.gtag) {
gtag('event', 'image_loaded', {
'image_name': img.alt,
'image_src': img.src
});
}
});
});
Mesurer le LCP
// Mesurer le Largest Contentful Paint
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP :', lastEntry.renderTime || lastEntry.loadTime);
});
observer.observe({entryTypes: ['largest-contentful-paint']});
Conclusion
Lazy loading maîtrisé : Utilisez loading="lazy" pour les images non-critiques, et définissez toujours les dimensions pour éviter le CLS.
Checklist lazy loading :
- ✅ Utiliser
loading="lazy"sur images au-dessous du fold - ✅ Définir
widthetheight(ou aspect-ratio) - ✅ Combiner avec srcset pour images responsives
- ✅ Tester le CLS avec DevTools ou PageSpeed Insights
- ✅ Implémenter fallback pour anciens navigateurs