Espace tutos dev’ & informatique 🐧

Le préchargement des images responsive, c’est possible

Publié le
Article sur le préchargement des images responsive

Introduction

Dans l’article d’aujourd’hui on va parler d’optimisation web, plus particulièrement du préchargement des images responsive. Cette technique est encore peu utilisée, pourtant elle peut s’avérer utile pour augmenter la rapidité du chargement par les visiteurs.

Tout d’abord on va revoir rapidement ce qu’est le préchargement des images. On verra ensuite comment le faire avec des images responsives, puis on présentera différentes situations et la forme de préchargement qui convient. Si vous êtes un développeur pressé, vous pouvez passer directement aux exemples de code 😉

Le préchargement des images

Précharger ses images, pourquoi faire déjà ?

Si vous lisez cet article vous savez déjà probablement ce qu’est le préchargement des images. Quoi qu’il en soit, je vais en représenter les bases.

En quelques mots, précharger une image, c’est forcer le navigateur à la charger avant le reste des ressources d’une page web, même si elle n’est pas immédiatement utilisée. Cela permet de la rendre disponible plus rapidement lors du rendu de la page, et donne une sensation de rapidité de chargement au visiteur. Au lieu d’avoir un espace vide sous les yeux le temps que l’image se charge, cette dernière s’affiche immédiatement.

La page affiche plus vite l'image préchargée que l'image non préchargée
L’image non-préchargée s’affiche plus tard et donne une impression de lenteur

De plus, dans le pire des cas, en plus d’afficher un espace vide, le chargement tardif de l’image peut causer un décalage du contenu de la page lorsque l’image est finalement affichée. S’il vous est déjà arrivé de cliquer par erreur sur un lien ou un bouton suite à un décalage soudain du contenu d’une page, c’était sans doute dû à un problème de chargement tardif d’image !

Mais quelle(s) image(s) faut-il précharger ?

Il parait logique de précharger les images suivantes :

☑️   Précharger les images dont l’affichage tardif est remarqué par le visiteur. Si en chargeant un site sur votre mobile en 3G il vous vient la pensée « tiens, c’est quoi ce vide » en regardant le haut de la page, alors il y a de bonnes chances que précharger cette image soit une bonne idée.

☑️   Précharger les images dont l’affichage tardif provoque un décalage du contenu. Si vous cliquez involontairement sur des liens car le contenu bouge alors il faut probablement précharger l’image qui est responsable.

Il ne faut cependant pas précharger trop d’images car cela peut ralentir le chargement des autres ressources, qui doivent attendre leur tour. On se limite souvent à une seule image, celle qui est la plus grande visible en haut de la page (above the fold en anglais).

Meme : preload all the things !
Eh non, précharger toutes les images n’est pas une bonne idée

Réduire le poids du fichier à précharger est également recommandé pour optimiser le temps de transfert.

PageSpeed Insights et Lighthouse à la rescousse

Comme il n’est pas forcément facile de trouver quelles images précharger, il existe des outils d’optimisation capables de nous aider. On peut citer par exemple Google Lighthouse (intégré dans les outils de développement de Chrome) ainsi que le site PageSpeed Insights (qui utilise Lighthouse en interne).

Faites le test, suivez le lien vers PageSpeed Insights et saisissez l’adresse d’une page dans le champ prévu à cet effet. L’outil analysera la rapidité de chargement de la page et cherchera diverses possibilités d’optimisation. Deux informations nous intéressent particulièrement dans le cadre du préchargement des images :

👉   L’élément de contenu le plus large est appelé le Largest Contentful Paint, abrégé LCP. Sur la plupart des pages il s’agit d’une image de taille imposante qui attire l’œil, le précharger a donc du sens.

👉   Le décalage au rendu du contenu de la page est appelé le Cumulative Layout Shift, abrégé CLS. Si une image est indiquée comme responsable d’un décalage, alors la précharger peut être un bon choix.

Analyse du Largest Contentful Paint et du Cumulative Layout Shift sur la page d'accueil du site codecolibri
Image Largest Contentful Paint en partie responsable d’un Cumulative Layout Shift

Il peut parfois arriver que Lighthouse et PageSpeed Insights n’arrivent pas à déterminer le contenu posant problème, dans ce cas c’est au développeur de trouver ce qui fonctionne le mieux. En général, ces outils donnent de bons résultats tout de même.

Préchargement non-responsive avec les balises <link rel=preload>

Une fois que l’on connait la ou les images que l’on veut précharger, il n’y a plus qu’à modifier le code source de la page pour l’indiquer. Cela se fait en ajoutant une balise <link rel=preload> dans la partie <head> du code. Il faut autant de balises que d’images à précharger :

<!-- Cette image sera préchargée -->
<link rel="preload" as="image" href="image.jpg" />

<!-- Celle ci aussi -->
<link rel="preload" as="image" href="photo.jpg" />

Le préchargement est possible avec la plupart des navigateurs plus récents que Internet Explorer 11. Quand la balise <link rel=preload> n’est pas supportée elle n’a aucun effet sur la page, on peut donc la garder sans risque.

Le préchargement est bien supporté sauf par Internet Explorer
On peut précharger partout sauf dans Internet Explorer (source caniuse.com)

Voila pour les bases, passons maintenant aux différents moyens de faire du préchargement responsive 🦉

Le principe de l’attribut media des balises <link> est simple. Il permet de spécifier une media query qui doit être vérifiée pour que la balise soit prise en compte. Si la media query n’est pas vérifiée, la balise <link> sera complètement ignorée par le navigateur.

D’un point de vue du préchargement responsive, cela permet de n’activer les balises <link rel=preload> que lorsque la taille d’écran est la bonne, par exemple :

<!-- Cette image ne sera préchargée que sur les petits écrans -->
<link
  rel="preload" as="image"
  href="image_small.jpg"
  media="screen and (max-width: 767px)" />

Il y a tout de même une subtilité. Les balises <link rel=preload> n’étant pas mutuellement exclusives, on peut se retrouver à précharger inutilement plusieurs tailles d’image si on n’est pas assez spécifique dans les media queries. Voyez plutôt :

<!-- INCORRECT : media queries pas assez spécifiques -->
<link rel="preload" as="image" href="image.jpg" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (max-width: 1023px)" />
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-width: 767px)" />

<!-- CORRECT : les media queries ne se chevauchent pas -->
<link rel="preload" as="image" href="image.jpg" media="screen and (min-width: 1024px)" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (min-width: 768px) and (max-width: 1023px)" />
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-width: 767px)" />

L’attribut media des balises <link> n’a rien de nouveau, il existait déjà sous Internet Explorer 6 et Firefox 2 👴 A ce titre, son support par les navigateurs est excellent :

L'attribut media des balises link est compris par tous les navigateurs
L’attribut media des balises link est compris par tous les navigateurs (source caniuse.com)

Ce n’est cependant pas parce que l’attribut media est supporté partout que le préchargement fonctionnera dans les navigateurs qui ne supportent pas <link rel=preload>. Exit IE11 donc, même s’il est en vert dans le tableau ci-dessus !

Les attributs imagesrcset & imagesizes des balises <link>

Vous connaissez sans doute les attributs srcset et sizes des balises d’image <img>. Ces derniers permettent de fournir différentes adresses d’image de différentes tailles pour une même balise, et d’informer le navigateur sur la taille de l’image qui sera affichée afin de l’aider à choisir le bon fichier à charger.

Fait bien pratique, les balises <link rel=preload> disposent des attributs imagesrcset et imagesizes qui font exactement la même chose, au point où l’on peut se contenter de copier-coller les valeurs des attributs de la balise <img> :

<!-- Image responsive avec srcset et sizes -->
<img src="image.jpg"
  srcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" />

<!-- Copier-coller les valeurs dans imagesrcset et imagesizes -->
<link rel="preload" as="image"
  href="image.jpg"
  imagesrcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" />

Notons qu’il est possible d’utiliser l’attribut media dont nous avons parlé plus haut en plus de imagesrcset et imagesizes. C’est utile si l’affichage de l’image est conditionné à une media query :

<!-- Préchargement responsive en portrait uniquement -->
<link rel="preload" as="image"
  href="image.jpg"
  imagesrcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
  media="screen and (orientation: portrait)" />

Les attributs imagesrcset et imagesizes des balises <link> sont encore un peu récents et à ce jour (février 2023) ne sont pas supportés par les navigateurs Safari et Safari pour iOS.

L'attribut imagesrcset des balises link est supporté partout sauf Safari et Internet Explorer
Le link imagesrcset est supporté hors Safari et Internet Explorer (source caniuse.com)

Ils ne sont pas supportés non-plus par Internet Explorer, mais il ne supporte pas le préchargement de toutes façons, ni même les attributs srcset et sizes des éléments <img> 🤷‍♀️

L’attribut fetchpriority des éléments <img>

Lorsque le navigateur charge les ressources d’une page, il tente de les récupérer dans un ordre de priorité déterminé par des techniques heuristiques (par exemple, le bas de page pourrait être considéré moins prioritaire que le haut de page, les petites images moins prioritaires que les grandes, etc.). Ces heuristiques sont spécifiques aux différents navigateurs, et ils ne se comportent pas de la même façon.

L’idée derrière l’attribut fetchpriority des éléments <img> est de permettre aux développeurs d’indiquer le niveau de priorité d’une image. Les différents niveaux à l’heure actuelle sont high (à charger en priorité), low (non-prioritaire) et auto (par défaut, priorité déterminée par les heuristiques). Cet attribut s’applique simplement sur la balise <img> :

<!-- Cette image est prioritaire et sera PEUT-ETRE chargée en premier -->
<img src="image.jpg" fetchpriority="high" />

<!-- Cette image n'est pas prioritaire et ne sera PEUT-ETRE PAS chargée en premier -->
<img src="image2.jpg" fetchpriority="low" />

<!-- Cette image aura sa priorité déterminée heuristiquement -->
<img src="image3.jpg" />

La grosse différence avec <link rel=preload> est que fetchpriority est indicatif alors que <link rel=preload> est impératif. Le navigateur prendra PEUT-ETRE en compte fetchpriority, alors que le préchargement avec <link rel=preload> sera TOUJOURS appliqué.

De plus, en matière de support, l’attribut fetchpriority est un petit nouveau ! Il est pour l’instant très limité, et il est probable que le comportement évolue au fil du temps. Seuls Chrome et Edgium le supportent à l’heure actuelle :

L'attribut fetchpriority est supporté sous Chrome et Edge
L’attribut fetchpriority est supporté sous Chrome et Edge (source caniuse.com)

Il n’est à utiliser qu’en dernier recours pour le moment donc.

Démonstrations de préchargement responsive

Précharger un élément <img> simple

Débutons avec quelque chose de facile 😄 Pour précharger une balise d’image toute simple, on utilise une balise <link rel=preload> dans la partie <head> de la page :

<!-- Image simple -->
<img src="image.jpg" />

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image.jpg" />

Précharger un élément <img> avec srcset et sizes

Pour les balises d’image responsive tout ce que nous avons à faire est de placer telles quelles les valeurs dans les attributs imagesrcset et imagesizes d’une balise <link> dans la section <head> de la page :

<!-- Image responsive -->
<img src="image.jpg"
  srcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" />

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image"
  href="image.jpg"
  imagesrcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" />

S’il y a en plus une media query conditionnant l’affichage de l’image en plus des attributs srcset et sizes, alors on peut utiliser l’attribut media en plus de imagesizes et imagesrcset :

<!-- Image responsive conditionnelle -->
<img class="portrait-only"
  src="image.jpg"
  srcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" />

<!-- Style qui affiche l'image selon l'orientation de l'écran -->
<style>
  .portrait-only{ display: none }
  @media screen and (orientation: portrait){
    .portrait-only{ display: inline-block }
  }
</style>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image"
  href="image.jpg"
  imagesrcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
  media="screen and (orientation: portrait)" />

Parfois il n’est pas possible d’insérer une balise <link>, dans ce cas, on peut se tourner vers l’ajout d’un attribut fetchpriority avec fetchpriority="high" sur la balise d’image elle-même :

<!-- Chrome, Edgium -->
<img src="image.jpg"
  srcset="image.jpg 1024w, image_medium.jpg 512w, image_small.jpg 256w"
  sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px" 
  fetchpriority="high" />

<!-- Rien à mettre dans le HEAD -->

Précharger une image de fond CSS background-image simple

Il est bien évidemment possible (et facile 😄) de précharger les background-image CSS non-responsive. On reprend la même méthode que pour les images simples :

<!-- Zone avec image de fond -->
<div class="fond">Cette div a une image de fond</div>

<!-- Style de l'image de fond -->
<style>.fond{ background-image: url(image.jpg) }</style>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image.jpg" />

Précharger une image de fond CSS background-image responsive

Le cas des images background-image responsive est un tout petit peu plus compliqué. En effet, là où les styles CSS dépendant de la taille d’écran sont capables de s’écraser les uns les autres pour au final ne proposer qu’une seule valeur de background-image, les balises <link> avec l’attribut media ne s’excluent pas mutuellement. On doit donc être exhaustif dans nos media-queries, au risque de précharger toutes les tailles d’image au lieu de la bonne !

<!-- Zone avec image de fond responsive -->
<div class="fond">Cette div a une image de fond</div>

<!-- Style de l'image de fond -->
<!-- On ne précise que max-width car les valeurs s'écrasent mutuellement -->
<style>
  .fond{ background-image: url(image.jpg) }
  @media screen and (max-width: 1023px){
    .fond{ background-image: url(image_medium.jpg) }
  }
  @media screen and (max-width: 767px){
    .fond{ background-image: url(image_small.jpg) }
  }
</style>

<!-- Préchargement à mettre dans le HEAD -->
<!-- On précise max-width ET min-width car les balises ne s'excluent pas mutuellement -->
<link rel="preload" as="image" href="image.jpg" media="screen and (min-width: 1024px)" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (min-width: 768px) and (max-width: 1023px)" />
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-width: 767px)" />

Ca se complique encore si on ajoute la gestion des densités d’image responsive. Cela multiplie le nombre de combinaisons pour la valeur de l’attribut media. Jugez par vous-même 😶

<!-- Zone avec image de fond responsive -->
<div class="fond">Cette div a une image de fond</div>

<!-- Style de l'image de fond -->
<style>
  /* densité 1 soit 96dpi */
  .fond{ background-image: url(image.jpg) }
  @media screen and (max-width: 1023px){ .fond{ background-image: url(image_medium.jpg) } }
  @media screen and (max-width: 767px){ .fond{ background-image: url(image_small.jpg) } }

  /* densité 2 */
  @media screen and (min-resolution: 192dpi){
    .fond{ background-image: url(image.jpg) }
    @media screen and (max-width: 1023px){ .fond{ background-image: url(image.jpg) } }
    @media screen and (max-width: 767px){ .fond{ background-image: url(image_medium.jpg) } }
  }

  /* densité 3 */
  @media screen and (min-resolution: 288dpi){
    .fond{ background-image: url(image.jpg) }
    @media screen and (max-width: 1023px){ .fond{ background-image: url(image.jpg) } }
    @media screen and (max-width: 767px){ .fond{ background-image: url(image.jpg) } }
  }
</style>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image.jpg" media="screen and (max-resolution: 191dpi) and (min-width: 1024px), screen and (min-resolution: 192dpi) and (max-resolution: 287dpi) and (min-width: 768px), screen and (min-resolution: 288dpi)" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (max-resolution: 191dpi) and (min-width: 768px) and (max-width: 1023px), screen and (min-resolution: 192dpi) and (max-resolution: 287dpi) and (max-width: 767px)" />
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-resolution: 191dpi) and (max-width: 767px)" />

Précharger des images affichées uniquement dans certaines conditions (display none…)

Il arrive parfois que l’on ait un élément qui ne s’affiche que sur certaines tailles d’écran et qui est masqué sur les autres (avec display: none et consorts). Il est tout à fait possible de gérer cela aussi dans le cadre d’un préchargement, toujours avec l’attribut media des balises <link> :

<!-- Image affichée selon l'écran -->
<img class="image" src="image.jpg" />

<!-- Style qui affiche l'image selon la taille de l'écran -->
<style>
  .image{ display: inline-block }
  @media screen and (max-width: 767px){ 
    .image{ display: none }
  }
</style>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image.jpg" media="screen and (min-width: 768px)" />

Si il y a une bascule d’affichage entre plusieurs éléments en fonction de la taille d’écran c’est exactement la même logique avec autant de balises <link> qu’il y a d’éléments :

<!-- Images selon l'écran -->
<img class="grand" src="image.jpg" />
<img class="moyen" src="image_medium.jpg" />
<img class="petit" src="image_small.jpg" />

<!-- Style qui affiche l'image selon la taille de l'écran -->
<style>
  .moyen, .petit{ display: none }
  .grand{ display: inline-block }
  @media screen and (max-width: 1023px){ 
    .grand, .petit{ display: none }
    .moyen{ display: inline-block }
  }
  @media screen and (max-width: 767px){ 
    .grand, .moyen{ display: none }
    .petit{ display: inline-block }
  }
</style>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image.jpg" media="screen and (min-width: 1024px)" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (min-width: 768px) and (max-width: 1023px)" />
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-width: 767px)" />

Cela fonctionne également avec des éléments dotés d’images de fond background-image 👍

Précharger un élément <picture> responsive

Les éléments <picture> sont un peu l’artillerie lourde du responsive, mais on ne les rencontre pas très souvent en comparaison avec les balises <img> – en partie à cause de l’absence de support pour ses fonctionnalités dans les vieux navigateurs (that’s Internet Explorer 11 for you).

Une utilisation commune des balises <picture> consiste à préciser pour chaque source d’image une media-query qui détermine quand l’image est affichée. On peut précharger dans ce cas avec l’attribut media des balises <link> comme on en a l’habitude maintenant :

<!-- Image responsive -->
<picture>
  <source srcset="image_small.jpg" media="screen and (max-width: 767px)" />
  <source srcset="image_medium.jpg" media="screen and (max-width: 1023px)" />
  <img src="image.jpg" />
</picture>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image" href="image_small.jpg" media="screen and (max-width: 767px)" />
<link rel="preload" as="image" href="image_medium.jpg" media="screen and (min-width: 768px) and (max-width: 1023px)" />
<link rel="preload" as="image" href="image.jpg" media="screen and (min-width: 1024px)" />

La balise <picture> supporte aussi les attributs srcset et sizes dans ses entrées <source>. Le résultat est analogue aux balises <img>, du coup on peut précharger en utilisant les attributs media ainsi que imagesrcset et imagesizes :

<!-- Image responsive en fonction du thème sombre / clair -->
<picture>
  <source 
    srcset="dark.jpg 1024w, dark_medium.jpg 512w, dark_small.jpg 256w"
    sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
    media="screen and (prefers-color-scheme: dark)" />
  <source 
    srcset="light.jpg 1024w, light_medium.jpg 512w, light_small.jpg 256w"
    sizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
    media="screen and (prefers-color-scheme: light)" />
  <img src="light.jpg" />
</picture>

<!-- Préchargement à mettre dans le HEAD -->
<link rel="preload" as="image"
  href="dark.jpg"
  imagesrcset="dark.jpg 1024w, dark_medium.jpg 512w, dark_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
  media="screen and (prefers-color-scheme: dark)" />
<link rel="preload" as="image"
  href="light.jpg"
  imagesrcset="light.jpg 1024w, light_medium.jpg 512w, light_small.jpg 256w"
  imagesizes="(max-width: 767px) 200px, (max-width: 1023px) 400px, 768px"
  media="screen and (prefers-color-scheme: light)" />

On peut aller encore plus loin avec <picture> par exemple dans la gestion des types de fichiers (citons le populaire format d’image optimisé WEBP), mais cela sort du cadre de cet article sur le responsive 🤓

Télécharger les démonstrations

L’ensemble des codes sources des démos de préchargement responsive peut être téléchargé dans une archive zip disponible ici. Cela pourra servir si vous avez à tester sur un navigateur en particulier, mais n’oubliez pas que le comportement d’un navigateur peut varier entre des fichiers HTML locaux et des pages web en ligne.

Conclusion

Et voila, on en sait plus sur le préchargement des images responsive et on dispose d’un nouveau levier pour optimiser le chargement de nos pages web ! Etant donné qu’il est impossible de ne pas avoir un site internet responsive de nos jours, nul doute que cette technique nous sera très utile 😉

Bon code à vous, à la prochaine ! Comme d’habitude, si vous avez besoin d’un coup de pouce pour votre site web je suis disponible via le formulaire de contact du site.

Partagez cet article  =>