Photo de Nitish Meena sur Unsplash

Chargement d'images asynchrone 2.0(18)

Thomas (@Enwin)

La récente sortie de la recommandation d’HTML 5.2 par le W3C a permis d’étendre encore plus les possibilités qui s’offrent à nous lors d’un développement de site. Malheureusement, certains comportements répandus sur Internet, comme le chargement des images à la demande, n’ont toujours pas trouvé écho auprès du W3C. À ce jour, charger des images à la demande n’est toujours pas pris en charge directement par le navigateur. Nous allons voir ici quels choix nous avons fait pour allier performance, légèreté du code et compatibilité.

Le besoin

La refonte du site étant terminée, nous pouvons maintenant nous attarder sur certains points techniques de cette refonte. Comme vous avez pu le constater en vous rendant sur notre page d’accueil, notre direction artistique requiert une somme conséquente d’images. Dans le cas le plus extrême, une résolution de 1600 pixels par 480 sur un écran en haute définition (écran utilisant au minimum le double de pixels pour afficher un seul pixel), le site charge 44 ressources graphiques (images de fond et images de contenu) pour un poids total de 4,45 Mo (3 Mo si votre navigateur comprend le format webp). Pour un site d’une page cela paraît un peu excessif. Afin d’optimiser le premier chargement de la page et de ne pas pénaliser les visiteurs s’ils ne parcourent pas tout le site, nous avons donc mis en place un chargement asynchrone pour les images.

Charger une image

La plupart des tutoriels disponibles en ligne expliquant comment charger une image à la demande utilisent une balise image sur laquelle le chemin de l’image à charger est définie dans un attribut de données (aussi appelé data attribute). L’attribut src est dans ce cas absent. Afin de charger l’image lorsqu’elle est requise, JavaScript est utilisé pour changer l’attribut de donnée en attribut src.

Exemple d'implémentation et son rendu
<img data-src="/mon-image.webp" alt="" />
Exemple de code à utiliser pour charger une image à postériori
<img src="/mon-image.webp" alt="" />
Code de l'image une fois la ressource chargée

Dans une optique de légèreté du code, nous avons tout d’abord retenu cette solution pour notre site. Cependant, une fois passé le code au validateur W3C (c’est toujours une bonne pratique, nous vous le recommandons) quelques difficultés ont surgi.

  • Problème no1 : « L’attribut src est obligatoire ». Un attribut src vide fera l’affaire.
  • Problème no2 : « L’attribut src ne peut pas être vide ». Un attribut src vide ne fait pas l’affaire.

Charger un image à postériori en se basant sur une balise image ne permet pas d’être valide W3C. Il est possible de remplir l’attribut src avec la représentation encodée d’une image (data-uri) mais ce choix n’a pas été retenu car bien que compressé par gzip, nous ne souhaitions pas alourdir inutilement la page. L’idée fut donc d’utiliser une balise <span>, un élément ne communiquant aucun sens sémantique, comme élément de substitution pour les images à charger. Une fois le script lancé, les balises <span> seraient remplacées par les images finales et la page serait valide W3C. Dans ce cas de figure, le document est malheureusement vide d’images pour les robots parcourant les sites afin de les référencer sur les moteurs de recherche. De plus, que ce soit cette solution ou d’autres trouvées en ligne, aucune ne fonctionne lorsque JavaScript n’est pas activé. Parcourir un site sans JavaScript est de moins en moins commun, mais cela reste une réalité pour certains utilisateurs — travaillant dans un grand groupe bloquant JavaScript à ses salariés par exemple ou ceux le faisant sciemment afin d’éviter tous les traceurs et publicités pouvant inonder un site. Voyons voir si en 2018 il est encore possible de se soucier des utilisateurs qui n’ont pas JavaScript.

HTML 4 à la rescousse !

Comment afficher une image qui est sensée être manipulée par JavaScript lorsque celui-ci n’est pas activé ? Utiliser une technique de substitution lorsque le cas se présente. Pour cela, la spécification d’HTML nous propose depuis 1998 et HTML 4 la balise <noscript>. Lorsque JavaScript n’est pas activé, le contenu d’une balise <noscript> est rendu par le navigateur dans le cas contraire la balise et ses enfants ne sont pas analysés. Par contre, si JavaScript se retrouve bloqué par une erreur, la balise <noscript> ne permettra pas d’afficher quelque chose. Utiliser une balise <noscript> contenant l’image à afficher nous ajoute alors une sécurité. Cependant, lier ce code à une balise <span> servant d’élément de substitution n’est pas très élégant, l’information se retrouvant ainsi dupliquée : le chemin de l’image est défini deux fois dans le code source. La réflexion peut être poussée encore plus loin.

.textContent + .insertAdjacentHTML() = BFF

Bien que la balise <noscript> ne soit pas analysée lorsque JavaScript est activé, elle fait partie du DOM et est donc sélectionnable via ses méthodes. Cette possibilité nous permet d’utiliser nos deux nouveaux amis : .textContent() et .insertAdjacentHTML(). .textContent(), utilisée sur la balise <noscript>, retourne une chaîne de caractère représentant son contenu : la balise image. Cette chaîne de caractères est ensuite déplacée, à l'aide de la méthode .insertAdjacentHTML(), à côté de la balise <noscript>. Une fois cela fait, la balise <noscript> peut être supprimée car devenue inutile et notre image s’affiche sur le site.

Implémentation HTML et JavaScript de notre solution
<noscript class="img">
  <img alt="" src="/mon-image.webp" />
</noscript>
Implémentaion HTML
const noscript = document.querySelector( ‘noscript.img’ );
const parent = noscript.parentElement;
const htmlImg = noscript.textContent;

noscript.insertAdjacentHTML( 'beforebegin', htmlImg );
parent.removeChild( noscript );
Implémentation JavaScript

Libre à vous d’ajouter un écouteur sur le chargement de l’image si vous souhaitez lui ajouter un effet d’apparition.

En résumé

Charger des images progressivement et à la demande est une bonne pratique permettant d’afficher une page plus rapidement lors du premier chargement. Cela permet aussi au petites connexions ou aux connexions pour lesquelles le prix du mégaoctet est coûteux de ne charger que ce dont l’utilisateur à besoin. Vous pouvez vous rendre compte de son importance en parcourant les innombrables résultats de recherche. Ces implémentations sont elles aussi nombreuses et nous n’avons pas la prétention de proposer une solution pour les remplacer toutes. Notre besoin était de fournir un code HTML valide en ne proposant pas de balise image sans attribut src. Cette solution tient également compte des cas de figure les plus courants grâce à l'emploi de la balise <noscript> pour une dégradation gracieuse lorsque JavaScript n’est pas actif. Cela permet aussi aux moteurs de recherche de référencer les images. Malheureusement, dans notre périple vers un code léger et performant, nous avons perdu Internet Explorer 8 en route. Celui-ci ne permet en effet pas d’accéder au contenu de la balise <noscript>. Si vous devez prendre en compte IE8 dans vos développements, vous avez toujours la possibilité d’utiliser les commentaires conditionnels pour écrire une balise image par exemple.

Dans le futur, plus ou moins proche suivant votre périmètre navigateur, il sera possible d’utiliser les éléments sur mesure (Custom Elements) pour générer une nouvelle balise qui aura un comportement de chargement asynchrone par défaut. Peut-être sera-t-il possible d’y ajouter une balise <img> en solution de rechange comme il est possible avec la balise <picture>. Si vous souhaitez avoir un aperçu de ce qui est possible je vous propose d’aller voir l’exemple créé par Monica Dinculescu (@notwaldorf) appelé lazy-image.

Références