Laisser un commentaire

Guide ultime des images responsives & retina ready

La “technologie” Retina, si on peut l’appeler ainsi, consiste en une densité de pixels assez élevée pour que l’œil (d’où le nom de retina) ne puisse discerner les pixels sur l’écran. Cependant, avec une telle densité, pour ne pas que tout paraisse minuscule, la valeur en pixel de chaque élément est démultipliée. Concrètement, cela signifie que dans votre feuille CSS, tout élément que vous définissez, par exemple, avec une width: 100px; fera en réalité 200px. Tout est multiplié par deux.

À de telles résolutions, autant dire que vos images peuvent vite paraître pour le moins pixelisées. Il faut donc penser aux différentes tailles d’écrans, mais aussi aux différentes densités d’écans. Un 22” HD (1920x1080) n’aura pas besoin de la même qualité d’image qu’un 22” en 4k (4096x2016). Voyons comment penser nos images pour qu’elles soient éclatantes en toute situation.

Selon la densité de l’écran, le coefficient de multiplication est variable. Il peut aller de x1,5 à x4 et plus. Vous pouvez lire le commentaire de Reimax pour plus de précisions.

Définition et résolution

Commençons par clarifier deux notions qui sont très souvent source de confusion. La définition d’une image numérique fait référence à ses dimensions en pixels, soit 1000x1000px, 1920x1080 etc. C’est simplement le nombre de pixels en longueur fois le nombre de pixels en largeur. L’opération donne le nombre total de pixels que contient l’image.

Quant à la résolution, elle concerne la densité d’information d’une image (ou d’un écran). Ainsi, pour une taille donnée, une image peut avoir plus ou moins d’informations. On parle généralement de ppi ou ppp en fançais pour les images numériques et de dpi ou ppp en français pour l’impression.

Dans le cas du numérique, on compte le nombre de pixels par pouces, tandis que dans l’imprimerie, il s’agit du nombre de points (les petites goutes d’encre) par pouce.

Ainsi, une image de 1000x1000px à 100dpi fera 10”x10” (25,4cm). Si l’on veut que la même image possède une résolution de 300dpi, cette dernière devra être affichée sur une surface de 3,33x3,33” (8,5cm). Si l’on veut conserver la même taille physique d’image pour une résolution de 300dpi, alors notre image source devra être de 3000x3000px.

On comprend donc que la résolution est un rapport entre la définition et la dimension qu’occupe l’image. C’est pourquoi, sur les supports numériques, parler de la résolution d’une image n’a que peu de sens. Une image de 300x300px affichée sur un écran sera affichée à la taille de 300x300px, le ratio est donc de 1:1.

Et le rétina fut

Il y a une petite exception à ce que je viens de dire plus haut : les écrans à haute densité – plus communément appelés rétina. Ce type d’écran possède une densité de pixels très importante. À tel point que l’œil ne peut plus discerner les points des pixels (d’où le nom de rétina…).

illustration humoristique des écrans retina d'Apple

Retina est une marque d’Apple, la majeure partie des fabricants proposent aujourd’hui des écrans à haute densité de pixels. Le terme rétina est utilisé dans l’usage courant de façon générique pour désigner les écrans à haute résolution.

Il n’y a pas de norme particulière permettant de définir qu’à partir d’une certaine densité de pixels par pouce, un écran est rétina ou non. À partir d’une certaine distance, tout écran devient rétina. C’est pourquoi, les écrans de téléphone – que l’on regarde de près – nécessitent en général une densité plus importante que ceux des ordinateurs.

Cela a pu varier selon les époques et les constructeurs, mais traditionnellement, les écrans présentaient une résolution de 96dpi. Ainsi, le rétina correspond à 2x96 soit une résolution de 192dpi, mais il existe des densités bien plus importantes.

Avec ce type d’écran, si on maintenait un ratio de 1:1, tout serait tout petit. Donc pour conserver des proportions raisonnables, tout est affiché avec un ratio de 2:1 ou plus. Le résultat ? Une image de 1000x1000px est affichée comme une image de 2000x2000px. Ainsi, plusieurs pixels physiques de l’écran affichent le même pixel de l’image, d’où la perte visible de qualité.

Pour un résultat optimal, il faudrait fournir une image de 2000x2000px à afficher en 1000x1000px. Comme ça, chaque pixel de l’image correspond à un pixel de l’écran. Évidemment, pour des écrans à densité encore plus importante, il faut des images d’encore meilleure qualité.

Vous pouvez tester votre écran avec mydevice.io. Ce site analyse les caractéristiques de l’écran depuis lequel vous visionnez la page et vous donne toutes les infos sur la résolution et la définition : taille en pixels physique, taille en pixels CSS, densité de pixel, CSS ratio etc.

Les formats d’images

À chaque situation sa solution. Il y a d’innombrable formats d’images, entre les images vectorielles et matricielles, voyons la meilleure option pour chaque cas d’usage.

Les vecteurs, parés en toute circonstance

Évidemment, une image vectorielle fait fi des notions de pixels. Elle est vectorielle, elle ne contient donc pas un ensemble de points à afficher, mais plutôt les coordonnées géométriques des formes qu’il faut rendre. Ainsi, elle se redimensionne à l’infini. Donc qu’il s’agisse de densité ou de taille, un seul SVG convient pour toutes les configurations.

Pour tout ce qui est logo et image créé avec des outils tels qu’Illustrator ou Inkscape, n’hésitez pas, utiliser le SVG ! C’est léger, très bien supporté par les navigateurs et ce sera toujours d’une parfaite qualité.

L’inconvénient, c’est que le SVG n’est pas adapté pour les photos par exemple. Traduire un paysage ou un portrait en éléments vectoriels revient à faire un fichier extrêmement lourd ou à perdre un nombre considérable de détails. Dans ces cas là, pas le choix, il faudra se replier sur les formats matriciels.

Les formats bitmap

Les images raster, bitmap ou encore matricielles en français, sont celles qui contiennent les informations des pixels, par opposition aux images vectorielles. Il existe des centaines de formats. Nous ne verrons que ceux utilisables sur le web.

Vous connaissez certainement les trois formats les plus populaires et nous allons en introduire un quatrième :

gif
C'est un format lossless, la qualité reste constante, il permet la transparence (d'une couleur seulement), l'animation et fonctionne avec une palette de 256 couleurs. Il est donc à utiliser pour les images aux formes géométriques principalement. Entre le svg et le png, sauf pour les animations, il n'est aujourd'hui plus très concurrentiel.
png
Il s'agit aussi d'un format lossless et il existe en deux versions : le png8 (pour 8 bits) qui embarque une palette de 256 couleurs ; et le png24 (pour 24 bits) qui fonctionne en mode couleur directe et couvre plusieurs millions de couleurs. Étant donné qu'il est lossless, les images présentant des formes clairement définies profiteront de la netteté du png. Il aura de bons résultats en terme de compression sur des images présentant de larges zones de couleurs homogènes. De manière générale cependant, il est bien plus volumineux que le jpeg.
jpeg
Le jpeg compresse avec perte. C'est à dire que la compression du jpeg élimine des informations de l'image. On perd des détails. De ce fait, c'est aussi le format qui fournit les fichiers les plus légers. On peut définir la qualité de compression du jpeg. Plus la qualité est basse, plus la compression sera agressive et plus on perdra en détails. Le jpeg est très adapté aux images avec de nombreux détails et du bruit. De par le fonctionnement de son algorithme de compression, on constatera rapidement une altération de la qualité au niveau des dégradés de couleurs et des formes précises (texte par exemple) dès lors que le niveau de compression est un tant soit peu élevé.
webp
Il s'agit d'un format open source créé par Google avec et sans perte, qui supporte la transparence, les animations et fonctionne en mode de couleur directe. À qualité égale, il offre une taille inférieur de 20 à 40% au png dans son mode lossless et au jpeg dans son mode lossy. Le support navigateur n'est pas encore à 100% mais on approche du 100% si on exclu Safari.

En résumé, dans la plupart des cas pour la photo, tentez le jpeg. S’il y a de gros aplats de couleur où des formes géométriques très précises qui “bavent” un peu en jpeg, tentez le png. Pour le webP, nous verrons qu’il est possible de l’utiliser en complément des autres formats dans certains cas. Il ne pourra cependant pas faire cavalier seul pour le moment.

Place au code

Assez discuté théorie, voyons ce que l’on peut faire concrètement !

Les images en CSS

Une partie non négligeable des images utilisées sur nos sites le sont à des fins décoratives. À cet effet, il arrive bien souvent qu’elles soient gérées par CSS via la propriété background.

.header {
    // image de 768px de large
    background-image: url('/img/meeting-s.jpg');

    // l'image est centrée et recouvre intégralement la zone
    background-position: center;
    background-size: cover;
 }

 @media (min-width: 769px) {
    .header {
        // image de 992px de large
        background-image: url('/img/meeting-m.jpg');
    }
 }

@media (min-width: 992px) {
    .header {
        // image de 1200px de large
        background-image: url('/img/meeting-l.jpg');
    }
}

@media (min-width: 1200px) {
    .header {
        // image de 2500px de large
        background-image: url('/img/meeting-xl.jpg');
    }
}

On a là un exemple classique d’une image de fond gérée en CSS et qui répond à différentes tailles d’écrans. On utilise par défaut une petite image puis, grâce aux media queries, en fonction des tailles d’écrans (respectivement > 768, 991 et 1200px), on utilise une image de plus grande définition pour qu’elle corresponde à la largeur de l’écran.

Les média queries permettent également de cibler les résolutions d’écrans. C’est en outre très bien supporté par les navigateurs. Il n’y a donc plus qu’à adapter notre scénario pour qu’il utilise une image adéquate en fonction de la définition et de la résolution de notre écran !

.header {
    // image de 768px de large
    background-image: url('/img/meeting-s.jpg');

    // l'image est centrée et recouvre intégralement la zone
    background-position: center;
    background-size: cover;
}

@media (min-resolution: 192dpi) {
    .header {
        // image 2x plus grande : 1536px de large
        background-image: url('/img/meeting-s@2x.jpg');
    }
}

@media (min-width: 769px) {
    .header {
        // image de 992px de large
        background-image: url('/img/meeting-m.jpg');
    }
}

@media (min-width: 769px) and (min-resolution: 192dpi) {
    .header {
        // image de 1984px de large
        background-image: url('/img/meeting-m@2x.jpg');
    }
}

@media (min-width: 992px) {
    .header {
        // image de 1200px de large
        background-image: url('/img/meeting-l.jpg');
    }
}

@media (min-width: 992px) and (min-resolution: 192dpi) {
    .header {
        // image de 2400px de large
        background-image: url('/img/meeting-l@2x.jpg');
    }
}

@media (min-width: 1200px) {
    .header {
        // image de 2500px de large
        background-image: url('/img/meeting-xl.jpg');
    }
}

@media (min-width: 1200px) and (min-resolution: 192dpi) {
    .header {
        // image de 5000px de large
        background-image: url('/img/meeting-xl@2x.jpg');
    }
}

Dans cet exemple, nous ne prévoyons que les écrans à densité classique et les 2x, soit 192dpi et plus. Cependant, vous pouvez prévoir différents intermédiaires, avec notamment le 1,5x pour les écrans à 120dpi et les 3x pour les écrans de 300dpi et plus (les smartphones récents par exemple). Certains smartphones comme la gamme des “+” d’Apple (6+ etc) ainsi que l’iPhone 10 ont même des résolutions de plus de 400dpi.

Vous n’avez rien de plus à faire si le background-size est fixé à cover ou contain. En revanche, si votre arrière plan possédait “juste la bonne taille”, il faudra maintenant forcer celle-ci avec background-size pour que l’image apparaisse aux bonnes dimensions et dans la résolution supérieure. Sans quoi elle sera simplement deux fois plus grande à l’écran mais sans gain de qualité.

Cloudinary a développé un outil en ligne qui permet d’automatiser la générations des images destinées au différents breakpoints de manière automatique. À utiliser sans modération !

Les images en HTML

Jusqu’à présent, nous ne disposions que de la balise image classique <img src="…"> pour inclure des images dans nos pages. C’était quelque peu limité. Nous utilisions alors des hacks en détectant la résolution de l’écran via JavaScript et en injectant la source de l’image à posteriori. Tout cela est révolu car le HTML nous a finalement doté des bons outils.

Cette nouvelle syntaxe permet de répondre à quatre besoins différents :

Nous utiliserons respectivement les termes taille, dpi, mime et art pour les quatre besoins mentionnés ci-dessus.

Nous verrons l’introduction de deux nouvelles balises. Pour les anciens navigateurs qui ne les supportent pas, aucun problème. Il y a dans tous les cas la balise img qui servira de fallback, le reste sera ignoré. Quoi qu’il en soit, soyez rassuré, car aussi bien picture que source sont très bien supportées.

Dans les cas où les navigateurs prennent en compte les nouvelles balises et attributs, celles-ci écrasent les valeurs définient dans la src de img.

Utilisation d’images de différentes tailles

Il s’agit là simplement de faire du responsive classique. Si l’écran est plus grand, on propose au navigateur une image différente.

<img
    src="buzut-400.jpg" alt="Buzut AFK IRL"
    sizes="100vw"
    srcset="buzut-200.jpg 200w,
        buzut-400.jpg 400w,
        buzut-800.jpg 800w,
        buzut-1200.jpg 1200w">

On voit que la balise img gagne quelques propriétés. Ici, sizes n’a qu’une valeur et indique que l’image devra faire 100% de la largeur du viewport. Le navigateur peut donc choisir entre une image de 200, 400, 800 ou 1200px de large.

<img
    src="buzut-400.jpg" alt="Buzut AFK IRL"
    sizes="(min-width: 640px) 60vw, 100vw"
    srcset="buzut-200.jpg 200w,
        buzut-400.jpg 400w,
        buzut-800.jpg 800w,
        buzut-1200.jpg 1200w">

Dans cet exemple, on donne au navigateur une instruction supplémentaire grâce à la media query. Si la largeur de la fenêtre du navigateur est égale ou supérieure à 640px, l’image ne fera que 60% de la largeur du viewport, sinon elle fera 100%. Les images proposées sont les mêmes que dans l’exemple précédent. Le navigateur choisit celle qui lui convient le mieux.

Utilisation d’images haute résolution

Step numéro deux du responsive, comme nous l’avons vu plus haut en CSS, il s’agit de définir des images pour les écrans rétina.

<img
    src="buzut-1x.jpg" alt="Buzut AFK IRL"
    srcset="buzut-2x.jpg 2x, buzut-3x.jpg 3x">

Ici, on propose au navigateur une image par défaut, une autre correspondant au double de la définition et enfin une troisième correspondant au triple. Selon l’écran utilisé, le navigateur choisira la plus appropriée.

Utilisation de différents formats

Pour ce cas d’usage, deux nouvelles balises font leur apparition. Il s’agit de source et de picture. La première permet de spécifier différents médias accompagnés des éléments qui permettent au navigateur de décider ou non de l’utiliser. La seconde permet de regrouper les balises source et media.

<picture>
    <source
        srcset="buzut.webp"
        type="image/webp">

        <img src="buzut.jpg" alt="Buzut AFK IRL">
</picture>

Utilisation d’images différentes selon le contexte

<picture>
    <source
        media="(min-width: 1024px)"
        srcset="buzut-paysage.jpg">

    <img
        src="buzut-portrait.jpg" alt="Buzut AFK IRL">
</picture>

On fournit ici par défaut une image en gros plan. Si le navigateur fait au moins 1024px de large, on spécifie à ce dernier d’utiliser l’image en format panoramique.

On mixe le tout

On y va maintenant franco en utilisant ensemble les quatre possibilités vues précédemment.

<picture>
    <source
    media="(min-width: 1280px)"
    sizes="50vw"
    srcset="buzut-paysage-200.webp 200w,
            buzut-paysage-400.webp 400w,
            buzut-paysage-800.webp 800w,
            buzut-paysage-1200.webp 1200w,
            buzut-paysage-1600.webp 1600w,
            buzut-paysage-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 640px) 60vw, 100vw"
    srcset="buzut-portrait-200.webp 200w,
            buzut-portrait-400.webp 400w,
            buzut-portrait-800.webp 800w,
            buzut-portrait-1200.webp 1200w,
            buzut-portrait-1600.webp 1600w,
            buzut-portrait-2000.webp 2000w"
    type="image/webp">
    <source
    media="(min-width: 1280px)"
    sizes="50vw"
    srcset="buzut-paysage-200.jpg 200w,
            buzut-paysage-400.jpg 400w,
            buzut-paysage-800.jpg 800w,
            buzut-paysage-1200.jpg 1200w,
            buzut-paysage-1600.jpg 1800w,
            buzut-paysage-2000.jpg 2000w">
    <img
    src="buzut-portrait-400.jpg" alt="Buzut AFK IRL"
    sizes="(min-width: 640px) 60vw, 100vw"
    srcset="buzut-portrait-200.jpg 200w,
            buzut-portrait-400.jpg 400w,
            buzut-portrait-800.jpg 800w,
            buzut-portrait-1200.jpg 1200w,
            buzut-portrait-1600.jpg 1600w,
            buzut-portrait-2000.jpg 2000w">
</picture>

On résume tout ça ? Pour les navigateurs larges d’au moins 1280 pixels, la photo ne fera que 50% du viewport et on utilise la photo en mode panorama. Pour les navigateurs intermédiaires – entre 640 et 1279px – la même photo est utilisée mais elle fera 60% du viewport et pour les navigateurs encore plus petits, elle fera 100%. Dans chacun des cas, on propose au navigateur de choisir parmi différentes tailles d’images.

Lorsque les largeurs des images sont précisées, on n’utilise pas les notations nx. Le navigateur peut faire les maths lui-même. Enfin, si le navigateur supporte webP, il utilisera une image dans ce format.

Optimiser les images

Jusqu’ici nous avons parlé de formats et de tailles (définition et résolution). Ce n’est cependant pas tout : une image donnée dans une taille donnée et un fomat donné peut grandement varier en poids. Il va sans dire que dans le domaine du web, plus une image est légère, plus vite elle sera téléchargée, mieux c’est !

Commencons par le SVG. Ce format est absolument indépendant de la taille et est sans perte de qualité (ni des détails ni des couleurs). Nénamoins, gardez à l’esprit que les différents logiciels d’édition n’optimisent pas forcément les fichiers SVG. Avant de les mettre en ligne, je vous conseille de leur faire subir une petite cure d’amincissement.

Le cas des images bitmap est un peu plus complexe. Le format a une grande incidence, mais aussi les réglages d’export de l’image. Tous les outils d’édition permettent de régler la qualité d’export (échantillonage, taille de la palette de couleur ou qualité dans le cas du jpeg).

Dans le cas où vous avez de beaucoup d’images à traiter, de nombreux logiciels et services web permettent d’automatiser ce processus pour de multiples images. Le projet Imagemin possède une application simple et efficace qui fonctionne pour Windows, Linux et macOS.

Pour un besoin ponctuel, vous pouvez utiliser l’outil en ligne de TinyJPEG/TinyPNG ou ILoveIMG pour automatiquement optimiser jpeg et png, hyper pratique ! Le second offre une meilleure compression et augmente légèrement les contrastes.

Outils en ligne de commande

Les principaux formats possèdent des outils en ligne de commande permettant d’aisément optimiser des images déjà créés, même directement sur le serveur.

Nous allons recompresser à la volée toutes les images du répertoire courant pour le jpeg, le png et le svg respectivement avec : jpegoptim, optiPNG et svgcleaner.

Je vous laisse vous référer à leurs documentations respectives pour la procédure d’installation. Étant des outils Unix, ils s’installeront sans problème sur Linux et Windows (via WSL) ainsi que sur macOS (Homebrew ou Macports).

# jpegoptim pour le jpeg avec compression en qualité 80
# la qualité va de 0 à 100, 80 est donc une assez bonne qualité
# On enlève toutes les métadonnées (inutile pour le web)
# On prend ici soin de spécifier que les images peuvent avoir jpg ou jpeg en extension
jpegoptim -m80 --strip-all *.jpg && jpegoptim -m80 --strip-all *.jpeg

# On fait la même chose mais avec du jpeg progressif
jpegoptim -m80 --strip-all --all-progressive *.jpg && jpegoptim -m80 --strip-all --all-progressive *.jpeg

# On compresse les png
# on utilise strip pour enlever les metadatas également
# -o concerne l'agressivité de la compression, 5 est le juste milieu
optipng -o5 -strip all *.png

# Compression des svg
# Contrairement au deux outils précédents, il n'est pas possible d'écraser le fichier original
# La syntaxe est la suivante : svgcleaner in.svg out.svg
# on va donc utiliser une boucle for pour tout faire d'un coup
for $f in *.svg; do svgcleaner $f $f.optimised; rm $f; mv $f.optimised $f; done

En outre, comme il est rare que nos images sources soient en WebP, on peut automatiquement créer la version WebP pour les images en jpeg, png ou gif.

for f in *.jpg; do cwebp $f -o "$f".webp; done

Ensuite, deux solutions s’offrent à vous :

J’ai écris un article détaillé sur l’usage du WebP. J’y explique l’encodage et les différents moyens de servir les fichiers, dont les réglages pour Apache et Nginx.

Outils de build

Dans bien des cas, nous utilisons des outils pour le build ou le deploy. On peut alors automatiser la compression des images lors de cette étape. La plupart des processus de build se basent sur Node.js.

Ainsi, qu’on utilise directement les scripts npm, Webpack, Vue-cli, Gulp ou Grunt, sous le capot, c’est du Node.js. Dès lors, on a accès au projet Imagemin.

Imagemin n’est pas un logiciel en lui-même, c’est un projet qui regroupe un ensemble d’outils dédiés à l’optimisation d’image et offre une API unifiée permettant l’usage des différents outils dédiés aux différents formats. On y retrouve d’ailleurs jpegoptim et optiPNG dont nous avons utilisé dans la partie précédente.

L’avantage de ce projet est qu’il s’intègre avec les outils que vous utilisez : directement en Node.js, CLI, Gulp ou Grunt.

Je ne vais pas ici plus rentrer dans les détails car il faudrait un article dédié ! Vous avez cependant toutes les clefs pour intégrer imagemin dans votre workflow.

SaaS & CDN

Il est parfois plus simple ou adapté d’avoir recours à un service tiers. Par exemple, dans les cas où vous n’avez pas de processus de build, lorsque les images sont ajouté par les utilisateurs, si vous avez à gérer de très nombreux formats pour le responsive ou même les trois à la fois, ces services spécialisés offrent souplesse et efficacité.

Il y a deux fonctionnement distincs : soit vous utilisez l’API pour envoyer vos images et les récupérer dans le ou les formats souhaités, soit vous utilisez le service en tant que CDN. Dans ce second cas, il s’occupe de manière transparente de servir l’image la plus adaptée à votre utilisateur en fonction de la taille de la résolution de l’écran, des capacité de son navigateur etc.

La plupart des offres SaaS peuvent s’utiliser à la fois en tant que CDN ou uniquement comme une API. L’un des services les plus connus est Cloudinary. Par ailleurs, de nombreux opérateurs CDN offrent également l’optimisation des images, à l’instar de Fastly et CloudFlare.

De très nombreux autres services existent, bien que je ne les ai pas tous testé, je pense légitime d’en citer quelques-un : Optimole, imgIX, imagify, Gumlet ou encore TinyPNG dont nous avons parlé plus haut.

En dernier lieu, je voudrais également mentionner Thumbor. Il s’agit d’une API d’optimisation d’image open source. Vous pouvez donc lui déléguer vos tâches de modification et compression tout en conservant un contrôle intégral sur l’ensemble de votre infrastructure.

Voilà, nous avons fait le tour de tout ce qu’il faut savoir sur le responsive et le retina ! Des considérations matérielles au choix de la meilleur image en HTML ou CSS en passant par les différents formats, vous devriez être incollable. Et vos sites devront être parfaits en toutes situations.

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```