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.
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).
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.
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.
Voila pour les bases, passons maintenant aux différents moyens de faire du préchargement responsive 🦉
L’attribut media & les media queries des balises <link>
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 :
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.
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 :
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.