Différer le chargement des images hors écran sur mobile
Différer le chargement des images hors écran sur mobile
Différer les images sur mobile : la norme
La performance mobile est souvent freinée par la latence réseau (RTT) et la disponibilité du CPU du thread principal. Différer le chargement des images hors écran sur mobile résout ces deux problèmes en évitant la congestion de la bande passante sur le chemin de rendu critique et en répartissant les coûts de décodage des images sur la durée de la session.
Ce document explique comment différer efficacement les images sur mobile, quand utiliser cette technique et aborde les contraintes mécaniques spécifiques des viewports mobiles.

1. Différer les images hors écran sur mobile : lazy loading natif
Lorsqu'un navigateur charge une page, il ouvre un nombre limité de connexions parallèles (cela dépend de nombreux facteurs, mais 6 par domaine est une moyenne courante). Si ces connexions sont utilisées pour télécharger des images hors écran (par exemple, un logo de pied de page ou une diapositive de carrousel), le téléchargement des ressources critiques (généralement l'image LCP, les scripts importants et les polices) entrera en compétition pour les emplacements et la bande passante. Ce phénomène, connu sous le nom de congestion réseau, dégrade directement les Core Web Vitals.
En différant les images hors écran à l'aide de l'attribut natif loading, nous pouvons prioriser les ressources importantes et optimiser le chemin de rendu critique. Le navigateur ne récupère que ce qui est immédiatement visible, réservant la bande passante aux actifs qui impactent strictement le First Contentful Paint (FCP) et le Largest Contentful Paint (LCP). La méthode de lazy loading natif décharge cette logique de priorisation sur le mécanisme interne du navigateur, beaucoup plus rapide, supprimant le besoin de bibliothèques JavaScript anciennes et lentes.
Implémentation
Pour toutes les images situées sous le viewport initial ("la ligne de flottaison"), ajoutez l'attribut loading="lazy".
<!-- Image différée standard -->
<img src="product-detail.jpg"
loading="lazy"
alt="Vue latérale du châssis"
width="800"
height="600"
decoding="async">
Comment fonctionne le lazy loading sur mobile : l'heuristique du navigateur
Le lazy loading natif est supérieur aux solutions JavaScript car le navigateur ajuste le seuil de chargement (le moment où le téléchargement d'une image est déclenché) en fonction du type de connexion effectif (ECT).
- Sur 4G/WiFi : Le moteur Blink (Chrome/Edge) utilise un seuil conservateur (par exemple, 1250px). Il suppose une faible latence et ne récupère l'image que lorsque l'utilisateur est relativement proche du viewport.
- Sur 3G/Slow-2G : Le seuil s'étend (par exemple, 2500px). Le navigateur initie la requête beaucoup plus tôt par rapport à la position de défilement pour compenser les temps d'aller-retour élevés, garantissant que l'image est prête avant que l'utilisateur ne la fasse défiler dans la vue.
Exception critique : le candidat LCP
Une régression de performance courante survient lorsque les développeurs appliquent loading="lazy" à l'élément Largest Contentful Paint (LCP) (généralement l'image hero). Cela retarde la récupération jusqu'à ce que la mise en page soit terminée.
Stratégie LCP correcte : L'image LCP doit être chargée immédiatement (eager-loaded) et priorisée.
<!-- Image Hero : Chargement immédiat et priorisé -->
<img src="hero.jpg"
alt="Collection été"
width="1200"
height="800"
loading="eager"
fetchpriority="high"> 2. Complexités mobiles : Viewport et tactile
Les viewports mobiles introduisent des défis de rendu spécifiques que l'implémentation native gère de manière plus robuste que les solutions basées sur des scripts.
- Le Viewport : La zone rectangulaire visible de la fenêtre du navigateur. Sur mobile, elle est dynamique ; ses dimensions changent en fonction de l'orientation de l'appareil (portrait ou paysage) et de l'état du chrome du navigateur (barres d'URL qui se rétractent).
- La ligne de flottaison : Le bord inférieur exact du viewport. C'est le seuil qui sépare le contenu visible du contenu hors écran.
- Au-dessus de la ligne de flottaison : Tout contenu visible immédiatement au chargement de la page sans défilement. Les images ici sont souvent critiques et ne devraient presque jamais être chargées en lazy loading.
- En dessous de la ligne de flottaison : Tout contenu situé verticalement après la ligne de flottaison. Ce contenu est non critique et doit être différé jusqu'à ce que l'utilisateur défile à proximité.

Le Viewport dynamique
Sur les navigateurs mobiles, la hauteur du viewport (vh) est fluide. Lorsque l'utilisateur initie un défilement tactile, la barre d'URL et les contrôles de navigation se rétractent souvent, modifiant la taille de la zone visible.
Les bibliothèques de report d'images basées sur JavaScript calculent généralement la hauteur du viewport (window.innerHeight) une seule fois au début du chargement de la page. Lorsque les navigateurs mobiles redimensionnent dynamiquement la zone visible en masquant la barre d'URL lors d'un défilement, les méthodes JavaScript continuent d'utiliser l'ancienne valeur de hauteur, plus petite. Cela faisait que les images restaient non chargées même lorsqu'elles entraient physiquement dans la zone étendue du viewport, causant une mauvaise UX pour les visiteurs.
La gestion native corrige ce problème car le moteur de mise en page interne du navigateur suit automatiquement le viewport visuel, garantissant que les déclencheurs s'activent indépendamment des changements de taille du viewport.
3. Décodage d'images sur mobile et limitation du CPU
Les appareils mobiles ont un CPU limité et le décodage d'images sur mobile peut être relativement lent et coûteux. Convertir un JPEG en bitmap nécessite de nombreux cycles CPU. Sur un processeur mobile, le décodage d'une séquence de grandes images peut bloquer le thread principal pendant 50ms à 100ms chacune, causant une latence d'entrée.
La solution : content-visibility
Pour résoudre ce problème, nous pouvons utiliser la propriété et la valeur CSS content-visibility: auto. Cette propriété agit comme une norme pour le "Lazy Rendering". Elle indique au navigateur de contourner entièrement les phases de mise en page et de peinture pour les éléments hors écran. L'élément existe dans le DOM, mais il n'existe pas dans l'arbre de rendu jusqu'à ce qu'il s'approche du viewport.
Comme cette optimisation fonctionne en sautant le rendu du sous-arbre d'un élément, vous ne pouvez pas l'appliquer directement à une balise <img> (qui n'a pas de sous-arbre). Vous devez appliquer content-visibility au conteneur du produit ou à la carte image qui héberge ces images et son contenu
@media (max-width: 768px) {
.image-card, .product-card {
/* Sauter le rendu du conteneur et de ses enfants */
content-visibility: auto;
/* Essentiel : Empêche le conteneur de s'effondrer à une hauteur de 0px */
contain-intrinsic-size: auto 300px;
}
}
Cela garantit que même si une image est téléchargée, le navigateur ne paie pas le coût de mise en page/peinture jusqu'à ce que l'utilisateur la fasse défiler.
4. Méthodologies héritées : Pourquoi les éviter
Avant le support natif, les développeurs s'appuyaient sur JavaScript pour différer les images sur mobile. Ces méthodes sont encore largement utilisées mais doivent être considérées comme de la dette technique !
L'ère du "Scroll Handler" (2010–2016)
Les premières implémentations attachaient des écouteurs d'événements à l'événement scroll.
// OBSOLÈTE : Ne pas utiliser
window.addEventListener('scroll', () => {
images.forEach(img => {
if (img.getBoundingClientRect().top < window.innerHeight) {
img.src = img.dataset.src;
}
});
});
Blocage du Thread Principal : L'événement scroll se déclenche des dizaines de fois par seconde. Exécuter de la logique et calculer la mise en page (getBoundingClientRect) pendant un défilement actif causait des chutes de frame (jank).
Layout Thrashing : Interroger les propriétés géométriques force le navigateur à recalculer de manière synchrone le style de mise en page, une opération coûteuse en calcul sur les CPU mobiles.
L'ère de l'IntersectionObserver (2016–2019)
L'API IntersectionObserver a amélioré les performances en observant de manière asynchrone les changements de visibilité des éléments.
// DÉPRÉCIÉ : Utiliser le chargement natif si possible
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
Dépendance aux scripts : Cela nécessite l'exécution de JavaScript. Si le thread principal est occupé à hydrater un framework (React/Vue), les images restent non chargées même si elles sont dans le viewport.
Manque de conscience du réseau : Contrairement au chargement natif, IntersectionObserver utilise des marges fixes (par exemple, rootMargin: '200px'). Il n'étend pas automatiquement son tampon sur les réseaux lents, conduisant à des "flashs blancs" pour les utilisateurs sur des connexions médiocres.
Your dev team is busy.
Delegate the performance architecture to a specialist. I handle the optimization track while your team ships the product.
- Parallel Workflows
- Specialized Expertise
- Faster Delivery