Refonte de nos 3 millions de visuels produits - La dernière génération

Nous avons généré et utilisons nos nouveaux JPEG tout propres en production. Comme expliqué précédemment, le gain de cette première étape est principalement axé sur la qualité. En effet, les JPEGs générés ont un poids relativement proche de celui de nos anciens GIFs, mais avec une meilleure qualité visuelle.

Maintenant que notre première itération fonctionne correctement en production, on va se concentrer sur l’aspect performance Web du projet et essayer de résoudre cette problématique : comment réduire l’impact des visuels sur les performances de decitre.fr tout en gardant une bonne qualité d’affichage.

Ajout du WebP sur la plateforme

WebP est un format développé par Google depuis 2011, qui gère la compression avec ou sans perte ainsi que la transparence.

Depuis fin 2018, WebP est passé du statut de « format seulement supporté par Chrome » à celui de « format seulement non supporté par Safari ». Une belle progression en quelques mois après plusieurs années d’attente.

Cela tombe plutôt bien, car WebP annonce des images de meilleure qualité et plus légères que ce que le format JPEG peut fournir.

Configuration du benchmarking pour WebP

Comme pour les choix techniques autour de JPEG, nous allons lancer différentes analyses pour déterminer la valeur de qualité à utiliser pour le convertisseur WebP.

À nouveau, nous allons nous baser sur l’échantillon précédemment constitué pour générer et analyser des miniatures de nos visuels produits.

Par rapport à JPEG, la difficulté va être de pouvoir comparer la source et la version compressée.

Autre distinction avec JPEG, le format WebP gère la compression de manière différente. Là où JPEG va créer des artefacts grossiers lorsqu’on applique une faible valeur de qualité, WebP va « lisser » l’image en perdant en détail d’une façon plus subtile que ces fameux artefacts.

Évolution de la qualité en WebP de 20 à 100 par palier de 20

Dans l’exemple ci-dessus, là où JPEG aurait créé de gros artefacts sur les qualités basses, on constate que l’effet sur le sable se lisse. Plus la qualité baisse, plus l’impression d’un sable plat est présente.

Cela nous permet d’utiliser des versions dégradées de nos images avec une plus faible qualité et donc un niveau moindre de détail sans que cela soit choquant à l’œil.

Pour les différentes tailles d’images utilisées, nous avons fait tourner sur un échantillon des générations au format WebP avec différents niveaux de qualité. Puis, nous avons comparé les poids et le score DSSIM des WebP générés par rapport à ceux obtenus par rapport au JPEG généré et utilisé en production.

Pour rappel le score DSSIM est un calcul de dissimilarité entre images, qui nous permet d’évaluer si deux images sont similaires ou non. Nous avions déjà utilisé cet outil lors de la constitution du catalogue de visuels originaux.

Notre objectif est de générer une image WebP au moins aussi qualitative que le JPEG et plus légère.

Comparatif des gains pour les visuels utilisés dans nos widgets.

Par exemple, pour cette taille de visuel utilisée dans l’animation de decitre.fr, une qualité de 90 permettra d’avoir une image 40 % plus légère avec une dégradation similaire à celle du JPEG par rapport à l’image originale.

À noter que dans les transformations effectuées, nous préparons l’image (redimensionnement, modification de l’espace de couleur) puis nous l’optimisons.

Nous avons constaté de meilleurs résultats sur le poids et la qualité lors de l’optimisation et la conversion au format WebP si l’image en entrée du convertisseur WebP était au format PNG et non au format JPEG comme initialement.

Finalement, nous avons opté pour générer nos WebP avec une qualité de 90. C’était pour nous le meilleur compromis parmi la gamme d’images utilisées sur nos sites.

Mise en place des images pour les écrans à haute densité

Depuis quelques années, sur les téléphones, tablettes ou ordinateurs les écrans à haute densité de pixels sont devenus la norme.

Ces écrans proposent une très grande résolution sur une surface d’affichage réduite. Cela a pour effet de fournir des écrans avec un nombre de pixels par pouces élevés. L’objectif du Retina (appellation commerciale d’Apple pour ce type d’écran) est d’avoir une densité de pixel tellement élevée que l’œil humain ne puisse pas les décerner.

source CommitStrip

Pour ne pas avoir un affichage trop petit, ces écrans jouent sur le ratio de contenu et le passent à 2:1 (voire 3:1) contre 1:1 pour un écran desktop classique. Cela a pour effet d’afficher sur les terminaux mobiles des images agrandies avec un rendu un peu flou.

Vers la haute résolution !

Avec un format vectoriel, nous n’aurions aucun souci : nos images s’adapteraient sans dégradation sur les différents écrans. Mais avec du bitmap, comment faire ?

Pour éliminer l’effet de flou sur les écrans de ratio 2:1, il faut fournir une image dont on va doubler la largeur et la hauteur en pixel, mais en les faisant entrer dans l’espace normalement prévu (exemple : la version haute définition d’une image de 200x300 pixels sera de 400x600 pixels).

Mais avec quatre fois plus de pixels à afficher, on va forcément produire des images plus lourdes. Vu qu’on est sur un article qui aborde la performance Web, ça serait dommage. Surtout quand on réserve ces visuels à un usage mobile où il peut ne pas y avoir un réseau 4G pour tous.

La technique (qui peut paraître un peu grossière) que nous avons mise en place consiste à réduire la valeur de qualité pour nos images de ratio 2.

Pour ces images, en appliquant le ratio 2, la compression JPEG ou WebP est moins visible. Il faut donc trouver (encore une fois) le compromis entre poids de l’image par rapport à sa version en ratio 1 et valeur de qualité.

Nous avons donc, à partir de notre échantillon d’images préféré, généré nos visuels de ratio 2 en faisant varier la qualité de compression de 20 à 100 par incrément de 5 en JPEG.

Pour comparer, malheureusement, pas de DSSIM possible cette fois-ci. La qualité étant dégradée quand on regarde l’image dans ces dimensions réelles, les outils de calcul de dissimilarité ne nous seront pas d’une grande aide cette fois-ci.

Nous avons donc opté pour une validation manuelle et avons généré des tableaux de comparaisons pour déterminer au doigt mouillé la valeur de qualité à appliquer.

Du côté du format JPEG, malheureusement, les images générées en ratio 2 sont plus lourdes que celle en ratio 1. La valeur de qualité choisie était la plus basse possible sans qu’il y ait un effet de dégradation visuelle trop prononcé.

Pour le WebP, au contraire, on arrive à générer des visuels plus légers en ratio 2 qu’en ratio 1. Cela s’explique notamment par la différence d’algorithme entre les deux formats. En effet, on a vu que WebP, dans le cadre d’une compression agressive, détruisait moins l’image et n’altérait pas sa qualité globale à coup d’artefact de compression.

Nous avons donc pu descendre à des valeurs de qualité d’image très basses pour le format WebP (entre 20 et 30 selon les dimensions des images à afficher, contre plus de 60 au format JPEG), ce qui nous permet de générer des images plus légères en ratio que leur équivalent au ratio 1.

30 millions d’images

(la lala lala la lala lala la lalalalalalalaaaaaaa)

Une fois encore, on repart pour une génération des variantes de nos 3 millions d’images. En plus du WebP, on profite de cette génération sur la globalité du catalogue pour optimiser certains ratio 2 JPEG créés précédemment où un mauvais réglage entraînait, dans certaines conditions, la génération d’images pesant plusieurs mégaoctets contre quelques kilooctets.

16 jours, 13 heures et 28 minutes plus tard, nous avons généré les 30 millions de variantes en WebP et corrigé les 16 millions de JPEG non optimisés, portant notre catalogue total à plus de 60 millions d’images générées.

Nous avons désormais des WebP et des images de ratio 2 prêtes à être utilisées sur nos sites en complément des JPEGs utilisés en production.

Pour valider cette génération, nous avons extrait des données à partir de toutes les images qui composent notre catalogue produit.

Gain poids des WebP par rapport aux JPEG

Déjà, première confirmation : les fichiers WebP sont plus légers que les fichiers JPEG. On constate un gain médian de 40 % et au 90e percentile le gain est de l’ordre de 30 %. Cela s’annonce plutôt bien pour la performance Web :)

Du côté des images au ratio 2, le gain est moins évident.

Gain poids des JPEG en ratio 2 par rapport au ratio 1

Commençons par la comparaison des JPEG ratio 1 vs ratio 2. Le gain médian est de 16 % tandis qu’à partir du 79e percentile, les images JPEG au ratio 2 sont plus lourdes que leur version ratio 1. La courbe finit par amorcer une pente exponentielle à partir du 85e percentile.

Gain poids des WebP en ratio 2 par rapport au ratio 1

Maintenant, comparons les ratio 2 au ratio 1, mais au format WebP cette fois-ci. On retrouve une courbe similaire à celle vue précédemment. Le gain médian est cette fois de 13 %, tandis que le ratio devient plus lourd à partir du 82e percentile. Par contre, la hausse de poids est plus limitée et se contente d’atteindre 37 % de hausse au 99e percentile contre 1000 % pour la courbe JPEG.

Cette hausse de poids des images de ratio 2 par rapport à celle des ratios 1 n’est pas surprenante : on compare une image avec une version qui fait quatre fois plus de pixels où on tente de compenser l’augmentation de la taille de l’image par une plus forte optimisation. Il faut donc avoir une vigilance particulière sur les dix derniers pourcents des ratio 2 JPEG.

Toutefois le ratio 2 WebP permet d’être assez optimiste. La hausse de poids par rapport au ratio 1 WebP est à relativiser par rapport au gain global du format WebP par rapport au JPEG.

C’est d’ailleurs ce qu’il faut retenir : dans la quasi-totalité des cas, un WebP de ratio 2 sera plus léger qu’un JPEG de ratio 1.

LQIP ou le premier aperçu pas cher d’image produit & parfait complément du lazy loading

Le lazy loading

Tout d’abord, commençons par expliquer ce qu’est le lazy loading. Il s’agit d’une technique consistant à ne charger une ressource qu’au moment où on en a besoin.

Dans le cas de nos images, cela revient à ne déclencher leur chargement qu’à partir du moment où elles sont affichées à nos clients.

Sur decitre.fr, nous utilisons Lozad.js pour décaler le chargement des visuels produits, iframe, etc., et ainsi réduire l’impact sur les performances du chargement des images.

Voici un exemple de balise que nous utilisons sur notre site.

    <img 
        src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 
        title="Magento. Réussir son site e-commerce" 
        data-src="https://products-images.di-static.com/image/mickael-blanchard-magento/9782212125153-200x303-1.jpg" 
        class="lozad" 
    />

Le src contient une image de 1px par 1px au format GIF encodée en base64 et Lozad.js se charge de le remplir à partir du data-src lorsque l’image passe dans la zone d’affichage.

Ajouter les Low Quality Image Placeholder

Avec la solution présentée ci-dessus, notre image reste une zone blanche en attendant qu’elle soit chargée. Le lazy loading déclenche le chargement de l’image quelques pixels avant son affichage.

En théorie, sur une connexion rapide, on ne perçoit pas l’utilisation d’un lazy loader. Mais avec une connexion possédant une plus grosse latence ou un débit plus faible, on va avoir cet effet de clignotement le temps que l’image soit chargée.

Pour améliorer cette zone blanche, et proposer un premier aperçu des couvertures de nos produits, nous allons utiliser une version miniature de l’image qui sera utilisée en attendant que le lazy loader ait chargé le visuel définitif : c’est ce qu’on appelle les Low Quality Image Placeholders ou LQIP pour faire plus court.

Pour éviter un hit HTTP, nous allons comme pour le GIF de 1px, utiliser une version inlinée en base 64 posée directement dans le code HTML de la page.

Par contre, il va falloir que notre image en base 64 soit la plus légère possible pour ne pas alourdir trop notre code HTML. Pour cela, nous prenons l’image à afficher et nous lui appliquons les modifications suivantes :

  • conversion en JPEG,
  • d’une largeur et hauteur maximale de 20px,
  • avec une qualité de 20.

Ces réglages nous permettent de fabriquer une bouillie de pixel, certes reconnaissable, mais d’un poids acceptable aux alentours de 800 octets (un peu loin des 200 octets annoncés par Facebook).

    img.lozad:not([data-loaded]) {  
      filter: blur(10px);  
    }

Enfin, pour rendre cette bouillie plus agréable à l’œil, nous faisons appel à CSS et au filtre blur qui va nous permettre d’appliquer un flou sur ce tas de pixels extrapolé.

LQIP tout seul
LQIP avec blur
Image originale

Du côté des calculs, afin de ne pas faire assumer la génération de la miniatures à chaque appel sur le serveur, nous stockons en base de données le contenu binaire de l’image afin qu’il soit facilement intégrable dans nos pages.

C’est parti pour la mise en production 🚀 !

Pour rappel, nous avons sur decitre.fr des visuels produits JPEG en ratio 1.

Nous allons configurer les différentes combinaisons suivantes pour valider la performance Web :

  • JPEG ratio 1 et JPEG ratio 2
  • JPEG ratio 1 et WebP ratio 1
  • JPEG ratio 1 et LQIP
  • JPEG et WebP (ratio 1 et ratio 2)

Pour valider le bon fonctionnement de nos nouvelles images sur nos pages, nous nous sommes appuyés sur les outils d’analyse de la performance web comme Dareboost, GTmetrix ou WebPageTest que nous utilisons habituellement.

Nous nous sommes concentrés sur une fiche produit, dans un environnement desktop avec une connexion ADSL et un environnement mobile avec une connexion équivalent à de la 3G.

Impact des WebP

En comparant les poids totaux sur notre fiche produit, nous avons bien la confirmation que le format WebP permet d’avoir des pages plus légères.

WebP par rapport à JPEG ? (source : https://media.giphy.com/media/ZgYBhq1x7L1bW/giphy.gif)

En désactivant le lazy loading et donc en forçant le chargement de toutes les images présentes sur une fiche produit, la version avec JPEG charge 466 Ko d’image contre 323 Ko pour la version WebP. On obtient ainsi, pour notre exemple, un gain de près de 30 %.

Impact des images haute résolution

On l’a vu, les images hautes résolutions ne garantissent pas une réduction du poids. Au contraire, dans 20 % des cas, l’image haute résolution sera plus lourde que son équivalent basse résolution. Ce qui nous intéresse dans cette fonctionnalité sera donc la qualité.

Ratio 1
Ratio 2

Comme vous pouvez le constater sur ces captures faites à partir d’un téléphone, le principal intérêt de ces visuels est l’amélioration du rendu sur nos fiches produits.

Dans cet exemple, on se rend compte que l’image haute résolution permet d’améliorer la lisibilité de certains détails comme le résumé sur une quatrième de couverture ou le code barre d’un produit.

Et, le hasard faisant bien les choses, on gagne quelques Ko avec la haute résolution en passant de 40 Ko à 37,5 Ko pour ce visuel.

Impact du Low Quality Image Placeholder

Notre objectif avec cette fonctionnalité était d’afficher au plus tôt une version dégradée des visuels. Ainsi nos utilisateurs auront un meilleur ressenti de la performance grâce à ce visuel affiché le plus tôt possible quitte à ce qu’il soit dans un premier temps dégradé.

En haut avec LQIP, en bas sans LQIP

L’objectif semble atteint sur la comparaison image par image. Dès 750 ms, le rendu est terminé à 95 % contre 69 % pour la version sans LQIP.

En bleu foncé avec LQIP, en bleu clair sans LQIP

Cela est confirmé par l’évolution du SpeedIndex où la version LQIP nous permet d’atteindre les valeurs hautes plus rapidement.

Le mot de la fin

Avec ce travail sur les balises images de nos visuels produits, les nouveaux formats, la haute résolution, etc. se termine la seconde phase de notre chantier.

Il y a encore plusieurs pistes d’améliorations possibles à étudier de notre côté :

  • l’utilisation du header HTTP Accept pour profiter de la négociation de contenu et retourner le format optimal en fonction du support navigateur,
  • l’utilisation de l’objet NetworkingInformation (enfin le jour où il sera mieux supporté) et d’un service worker (en s’inspirant du travail de l’Équipe) pour basculer automatiquement sur une qualité d’image plus dégradée quand la connexion est en économie de bande passante
  • l’amélioration continue de la donnée source pour proposer des visuels originaux toujours plus qualitatifs et éliminer les dernières sources avec des dimensions inférieures à 200px.

Le projet aura été étalé dans le temps, mais nous avons pu le mettre en place itérativement, sans créer de chaos sur chacune des applications, tout en mettant en place de l’analyse et des métriques pour essayer de s’en sortir avec le volume de données à traiter.

Cet article clôt cette série sur la refonte de notre catalogue de visuels produits, quasiment 3 ans après démarrage du projet et après avoir atteint notre objectif initial de recentraliser tous ces visuels pour en fournir des versions plus qualitatives sur nos sites Webs.

Et puis, de toute manière, étant donnée la source d’inspiration des titres, autant faire en sorte que la trilogie d’articles s’arrête, elle, à 3 🙂.

Si vous trouvez une typo, n'hésitez pas à cloner et modifier le fichier. Merci d'avance :)

Laisser un commentaire ?

Commentaire

Greg Par Greg le 6 Février 2020 Répondre

Hello.

Merci beaucoup pour cet article. Il est très intéressant et bien écrit.

J’ai quand même deux questions et/ou interrogations.

  1. Nous avons utilisé la technique du lazy load sur un projet client pour 90% des images. Mais certains images (10%) devaient être référencées par Google. Nous n’avons pas trouver de technique simple pour palier ce problème. Comment avez vous gérer ce cas ?

  2. Il me semble que vous ne spécifiez pas de de width & height sur vos images. Cela veut dire que la zone de l’image n’est pas réserver par le browser. Quand l’image arrive, ca va provoquer un “repaint” complet de la page. Comment avez vous gérer ce cas ?

Sébastien Par Sébastien le 6 Février 2020 Répondre

Salut et merci :)

1 - Pour s’assurer que Google crawle les images, on pose en dessous de la version lazyloadée un tag imgcomplet dans une balise noscript. Par exemple pour une des images de l’article on a :

   <img class="lozad" width="200" height="275" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/posts/refonte-visuels-produits-3/lqip_blur.png" />
   <noscript><img src="/assets/posts/refonte-visuels-produits-3/lqip_blur.png" /></noscript>

2 - Pour le repaint c’était un des gros points faible du site. Avant tout ce travail on n’était pas en mesure d’avoir les dimensions de manières performantes. Vu qu’on avait un certain normalisme sur les dimensions des visuels, on fixait la hauteur du conteneur d’image en mettant la hauteur maximum possible, pour éviter les effets de clignotement une fois l’image chargée. Désormais, avec toute cette refonte, nous avons intégré dans nos visuels produits les dimensions des images. Mais c’est vrai qu’il manque encore les dimensions, notamment sur les images hors visuels produits.