Fork me on GitHub

Image RWD avec picture, srcset et sizes.

Afficher/Masquer les propositions de solutions

Principes abordés : Responsive Web Design

Prérequis :

Acte 1

Vous devez réaliser un container contenant trois images. En dessous de 640px (que vous convertirez en unités relatives, bien sûr), les trois images occupent 100% de la largeur disponible. Au dessus de 640px, les trois images se partagent la largeur disponible (chaque image occupant ⅓ de la largeur disponible).

Nous allons partir du code HTML suivant :

<!DOCTYPE html>
<html class="no-js">
  <head>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="images">
      <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" />
      <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" />
      <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" />
    </div>
  </body>
</html>

Et du code CSS suivant :

/* Pour éviter d'avoir un espace entre les images */
.images {
  font-size: 0px;
}

Utilisez une media query pour définir un tel découpage.

Il suffit d’ajouter une media query précisant qu’au delà de 640px (soit 40em sur une base de taille de police de 16px), les img sont alignées côte-à-côte et occupent ⅓ de l’espace disponible.

img {
  width: 100%;
}

@media screen and (min-width: 40em) {
  img {
    width: 33.33%;
  }
}

Acte 2

Vous disposez désormais de trois variantes pour l’image en trois largeurs différentes :

Pour faciliter la reconnaissance visuelle de la largeur de chaque image, nous avons modifié leur contenu mais dans la réalité, vous aurez probablement toujours une même image en plusieurs dimensions.

Votre objectif est d’utiliser, dans tous les cas, l’image de la meilleur qualité possible, quelle que soit la largeur de l’écran et la densité de pixel. Comme nous sommes dans un contexte purement HTML (nous ne modifierons pas les CSS ni le JS pour l’instant), cela veut dire qu’il sera du ressort du navigateur de choisir, au moment du chargement du code HTML, quelle image choisir en fonction des informations que vous lui aurez transmis.

Quelles informations passer au navigateur pour qu’il puisse disposer d’une vue d’ensemble et prendre les bonnes décisions ?

Avez-vous un moyen de connaitre à l’avance l’ensemble des largeurs et l’ensemble des densités de pixel que les internautes présenteront ? Le navigateur, au moment du chargement de la page, connait-il ces informations ?

Connaissez-vous la taille des images au moment du développement ? Savez-vous quelles seront les dimensions relatives de vos <img> par rapport au viewport. Le navigateur, au moment du chargement de la page, dispose-t-il de ces informations ?

Voici ce que nous savons au moment du développement et ce que le navigateur sait quand il charge une balide <img> classique.

Quoi ? Connu par le développeur à l’avance ? Connu par le navigateur avant de rendre l’image ?
Taille du viewport Non Oui
Taille relative de l’image par rapport au viewport Oui Non
Densité de l’écran Non Oui
Dimensions des images fournies Oui Non

Tout l’enjeu est donc d’informer le navigateur sur la tailles des images à disposition et leur largeur relative par rapport au viewport pour qu’il déduise, seul, l’image la plus pertinente.

Acte 3

srcset et sizes1 vont nous permettre de transmettre au navigateur les informations qui lui manquent.

Le support de <picture> n’est pas universel2, comment faire pour utiliser l’élément quand même ?

Utilisez pictureFill et renseignez un srcset sur les trois images.

Simuler une fonctionnalité indisponible sur un navigateur est une des tâches que l’on confère généralement à JavaScript, via un script appelé polyfill. pictureFill de Scott Jehl est un des meilleurs polyfills existants.

Attention : le navigateur est, dans cette méthode, responsable d’afficher la meilleure image possible. Quand une image est déjà dans son cache, il la prendra, sauf si une image de plus grande taille est nécessaire et téléchargeable. Pour le debug, veillez à utiliser votre navigateur en forçant le vidage du cache après chaque redimensionnement.

<!-- Code HTML avec srcset -->
<div class="images">
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" />
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" />
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" />
</div>

Acte 4

srcset permet de transmettre au navigateur des informations de largeur d’images mais nous n’avons donné aucune information sur la largeur relative de l’image par rapport au viewport. Le navigeur part donc du principe que l’image utilise 100% de la largeur du viewport. La mise en page sur 1/3 n’est absolument pas prise en compte dans le calcul.

Utilisez sizes pour expliquer au navigateur la taille relative de l’image.

<!-- Code HTML avec srcset et sizes -->
<div class="images">
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw" />
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw"/>
  <img src="http://traindrop.github.io/fiches/007/assets/320.jpg"
    srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw"/>
</div>

Acte 5

On vous donne désormais les trois mêmes images au format WebP, plus léger que le jpg :

Comment en tirer partie ?

La balise <picture> permet de proposer plusieurs éléments <source> dont le contenu viendra remplacer l’image en fonction des supports navigateurs, ici WebP.

Voir l’exemple.

<div class="images">
  <picture>
    <source type="image/webp" srcset="http://traindrop.github.io/fiches/007/assets/1024.webp 1024w, http://traindrop.github.io/fiches/007/assets/640.webp 640w, http://traindrop.github.io/fiches/007/assets/320.webp 320w" sizes="(min-width: 40em) 33vw,
     100vw">
    <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw" />
  </picture>
  <picture>
    <source type="image/webp" srcset="http://traindrop.github.io/fiches/007/assets/1024.webp 1024w, http://traindrop.github.io/fiches/007/assets/640.webp 640w, http://traindrop.github.io/fiches/007/assets/320.webp 320w" sizes="(min-width: 40em) 33vw,
     100vw">
    <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw" />
  </picture>
  <picture>
    <source type="image/webp" srcset="http://traindrop.github.io/fiches/007/assets/1024.webp 1024w, http://traindrop.github.io/fiches/007/assets/640.webp 640w, http://traindrop.github.io/fiches/007/assets/320.webp 320w" sizes="(min-width: 40em) 33vw,
     100vw">
    <img src="http://traindrop.github.io/fiches/007/assets/320.jpg" srcset="http://traindrop.github.io/fiches/007/assets/1024.jpg 1024w, http://traindrop.github.io/fiches/007/assets/640.jpg 640w, http://traindrop.github.io/fiches/007/assets/320.jpg 320w" sizes="(min-width: 40em) 33vw,
       100vw" />
  </picture>
</div>

Acte 6 (discussion)

Certains ne trouvent pas élégante la solution proposée par le composant picture.

À votre avis, pourquoi ?

  1. Exemple d’utilisation des attributs srcset et sizes sur MDN

  2. voir le support de l’élément <picture> sur CanIUse