L’attribut srcset pour des images responsive
L’attribut srcset
est un nouvel attribut pour les balises images décrit dans la spécification de HTML 5.1. Il permet de résoudre en partie l’une des plus grosses problématiques de l’intégration de sites responsive : les images.
Venant en complément de l’attribut src
habituel d’une balise <img>
, l’attribut srcset
permet de spécifier une liste d’images à afficher selon des critères particuliers. L’immense avantage, c’est que c’est le navigateur qui décide quelle image afficher, et que seule cette image sera téléchargée.
Son implémentation a déjà commencé depuis février dernier dans la version 34 de Chrome, mais limitée à la sélection d’image selon la densité de pixels de l’écran d’un appareil. Ainsi dans le code suivant, je peux préciser le chemin d’une image optimisée pour des appareils avec une densité de pixels à l’écran d’au moins 2 (aussi vulgairement appelés « écrans rétina »).
<img src="default.jpg" srcset="hidpi.jpg 2x" />
C’est déjà un bon début. Mais ce qui est vraiment bien, c’est que les navigateurs commencent maintenant à implémenter la suite, à savoir la sélection d’images selon le viewport. C’est en cours d’implémentation dans Chrome, Opera, et dans Firefox derrière un flag. Et surtout, ce sera activé par défaut dans Safari 8 sur iOS 8 et OS X Yosemite. Cela signifie que d’ici la fin de l’année, la majorité des navigateurs supporteront srcset. (Pour Internet Explorer, c’est pour l’instant « en considération ».)(On me fait signe que la version beta actuelle de Safari 8 ne supporte que la première partie de la spécification, et pas la sélection d’images selon le viewport).
Il y a donc de quoi être tout excité par cette nouveauté. J’ai fait mumuse avec srcset
ces derniers jours, et voici que j’ai appris et compris.
Tout d’abord, pour tester tout ça, il va falloir installer ou configurer les navigateurs compatibles. Pour avoir Safari 8 sur iOS, vous pouvez télécharger la beta de Xcode 6 dans laquelle vous aurez la dernière version du Simulateur iOS. Pour avoir Safari 8 sur OS X, vous pouvez télécharger la bêta publique de OS X Yosemite.
Pour Chrome, vous pouvez télécharger Chrome Canary (actuellement en version 38).
Pour Firefox, vous pouvez télécharger Firefox Nightly (actuellement en version 34). Puis il faudra activer le support de srcset
à la main. Pour ça, il faut aller à l’adresse about:config
, chercher les préférences dom.image.srcset.enabled
et dom.image.picture.enabled
, et les passer toutes les deux à la valeur true
.
Nous sommes prêts pour tester. Pour commencer, nous allons préciser dans srcset
une liste de quatre images : small.png (320×240), medium.png (640×480), large.png (1024×768) et xl.png (1920×1080). Si le navigateur ne supporte pas srcset, c’est l’attribut src qui prendra le relais avec l’image default.png (1024×768). Voici le résultat de ce premier exemple avec le code suivant :
<img src="default.png" srcset="small.png 320w, medium.png 640w, large.png 1024w, xl.png 1920w" alt="Test" />
Comme vous pouvez le constater, chaque image est accompagnée d’un « descripteur w
» (dixit la spécification, en opposition au « descripteur x
» utilisé lui pour la sélection selon la densité de pixels d’écran). J’ai eu beaucoup de mal à comprendre la signification de ce descripteur, notamment à cause d’article comme celui publié chez Alsacréations (désolé Geoffrey) qui confondent descripteur w
et largeur de viewport. À ce stade, il faut bien comprendre qu’il n’y a aucun lien entre les deux. Le descripteur w
est une indication laissée au navigateur sur la largeur en pixels de l’image correspondante.
Là où la spécification de srcset
est surprenante, c’est dans la façon de décrire comment un navigateur va choisir l’image à afficher :
L’agent utilisateur va calculer la densité de pixel réelle de chaque image à partir du descripteur
w
spécifié et des tailles de rendus spécifiées dans l’attributsizes
. Il peut ensuite choisir n’importe quelle ressource en fonction de la densité de pixels de l’écran de l’utilisateur, de son niveau de zoom, ou peut-être d’autres facteurs comme les conditions de réseau de l’utilisateur.
En gros : le navigateur fait ce qu’il veut. Cela signifie que Chrome pourrait décider de choisir l’image la plus petite si la connexion est un peu lente, alors que Safari pourrait au contraire décider de systématiquement choisir l’image la plus grande. Bienvenue dans un futur nouvel enfer du responsive !
À noter qu’actuellement, Firefox ne va charger une image qu’au chargement initial de la page, mais ne rafraîchira pas son affichage au redimensionnement de la fenêtre. La dernière version de Chrome Canary gère très bien ça au redimensionnement (et au passage, la nouvelle vue device mode des outils de développement de Chrome est vraiment bien foutue).
Les choses sérieuses vont commencer en introduisant l’attribut sizes
. Celui-ci permet de spécifier la largeur d’affichage de l’image selon des points de rupture. Dans l’exemple ci-dessus, les images vont toujours par défaut prendre 100% de la largeur du viewport. La spécification explique clairement que la valeur par défaut de l’attribut sizes
est de 100vw
et que dans ce cas, l’attribut peut-être omis. Malheureusement les développeurs de chez Mozilla ont du s’arrêter de lire la spécification juste avant ce paragraphe car dans l’implémentation actuelle, il faut impérativement préciser l’attribut sizes
, même pour une valeur de 100vw
. (J’ai remonté le problème, et il a déjà été affecté, donc on peut espérer une correction en moins de douze ans.)
Là où sizes
est bien pratique, c’est qu’il va permettre de spécifier différentes largeurs d’affichage pour notre image selon le reste du gabarit de notre page. Si par exemple je suis sur un viewport de 800px de large, mais que j’affiche mon image à seulement 400px de large, je peux le préciser dans l’attribut sizes
et le navigateur tentera de charger l’image qu’il juge la plus appropriée. En reprenant le premier exemple et en y appliquant cette condition, ça donnerait ce deuxième exemple et le code suivant :
<img src="default.png" srcset="small.png 320w, medium.png 640w, large.png 1024w, xl.png 1920w" sizes="(width:800px) 400px" alt="Test" />
Ici, l’image est toujours affichée à une largeur correspondant à 100% du viewport, sauf si celui-ci vaut exactement 800px, auquel cas l’image est fixée à une largeur de 400px. Cet exemple est totalement stupide dans la vraie vie. On pourra alors plutôt écrire ce genre de code un peu verbeux pour ce troisième exemple :
<img src="default.png" srcset="small.png 320w, medium.png 640w, large.png 1024w, xl.png 1920w" sizes="(min-width:20em) and (max-width:50em) 20em, (min-width:50em) and (max-width:80em) 40em, (min-width:40em) 10em" alt="Test" />
J’ai défini ici trois points de rupture précisant des tailles d’images respectivement de 20em, 40em et 10em. Si aucune des conditions des points de rupture n’est remplie, alors le navigateur appliquera à l’image sa valeur par défaut de 100vw.
Là où ça peut devenir un joyeux bazar, c’est qu’on peut combiner les descripteurs w
et x
. Et tout ça peut être combiné à la balise <picture>
qui elle aussi commence à être supportée. Opera a récemment publié un article pour donner des cas pratiques d’utilisation d’images responsive. L’article d’Eric Portis sur « srcset et sizes » est magnifiquement illustré et très complet sur le sujet et m’a aidé à comprendre un tas de trucs.