Fork me on GitHub

Image de fond RWD avec des media queries

Afficher/Masquer les propositions de solutions

Principes abordés : Responsive Web Design

Prérequis :

Acte 1

Vous devez réaliser un container de 800px de haut contenant lui-même trois zones. En dessous de 640px (que vous convertirez en unités relatives, bien sûr), les trois zones occupent 100% de la largeur disponible. Au dessus de 640px, les trois zones se partagent la largeur disponible (chaque zone est donc une colonne de ⅓).

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">
      <div class="resp-img"><!-- Ici un contenu --></div>
      <div class="resp-img"><!-- Ici un contenu --></div>
      <div class="resp-img"><!-- Ici un contenu --></div>
    </div>
  </body>
</html>

Et du code CSS suivant :

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

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

Il s’agit dans un premier temps de mettre une hauteur sur les div représentant les trois zones. Ensuite, il faut ajouter une media query précisant qu’au delà de 640px (soit 40em sur une base de taille de police de 16px), alors les div sont alignées côte-à-côte et occupent ⅓ de l’espace disponible.

.resp-img {
  height:800px;
}
@media screen and (min-width: 20em) {
  .resp-img {
    display: inline-block;
    width: 33.33%;
  }
}

Acte 2

Vous disposez d’une même image de fond (une texture répétable) en trois largeurs différentes :

Pour faciliter la reconnaissance des images, nous avons surimprimé les dimensions sur chaque image (ce qui n’arriverait pas en conditions réelles).

Vous devez mettre cette image de fond sur les zones portant la classe resp-img. Contrainte : nous sommes dans un contexte RWD donc vous ne savez pas quelle largeur l’écran fera. Votre objectif est d’utiliser, dans tous les cas, la variante de l’image de la meilleur qualité possible. Pour simplifier l’exercice, nous ne gérerons que les densités de pixel x1 et x2.

Astuce : il va vous falloir écrire un système d’équations prenant en compte la surface de rendu de l’image, la densité de pixel, la largeur de l’écran… et déduire des quatre cas possible (densité x1, moins de 640px ; densité x1, plus de 640px ; densité x2…) les media queries à ajouter (et il y en a un paquet !).

Commençons par mettre en place une image de fond. Nous allons utiliser la plus petite image possible par défaut (approche mobile first).

.resp-img {
  background-image: url(http://traindrop.github.io/fiches/006/assets/320.jpeg);
  background-align: center top;
  background-size: 100%;
}

Ensuite, il s’agit de définir si cette image est toujours la bonne en partant des contraintes connues et en essayant toujours d’avoir une image légèrement meilleure que nécessaire (les navigateurs savent mieux réduire qu’aggrandir). D’un point de vue mathémtique, cela donne largeur de l'image à afficher >= surface de rendu de l'image × densité de pixels. Or nous savons que la surface de rendu de l’image est égale à la largeur relative de rendu de l’image par rapport à la largeur totale, donc cela équivaut à : largeur de l'image à afficher >= surface relative de rendu de l'image × largeur de l'écran × densité de pixels. Il s’agit ensuite d’appliquer cette formule à différent cas.

Prenons le cas où la largeur de l’écran est inférieure à 640px, avec une densité de pixels de 1. Nous avons alors largeur de l'image à afficher >= 100% × largeur de l'écran × 1 ce qui donne largeur de l'image à afficher >= largeur de l'écran. On affichera donc l’image de 320px jusqu`à 320px (20em) de large et l’image de 640px jusqu’à 640px. L’image de 1024px ne sera pas utilisée dans ce cas car nous étudions le cas où la largeur de l’écran est inférieure à 640px.

Prenons ensuite le cas où la largeur de l’écran est supérieure à 640px, avec une densité de pixels de 1. Nous avons alors largeur de l'image à afficher >= 1/3 × largeur de l'écran × 1 ce qui donne largeur de l'écran <= 3 × largeur de l'image à afficher. On affichera donc l’image de 320px jusqu`à 960px (60em) de large, l’image de 640px jusqu’à 1920px (120em) et l’image de 1024px au delà.

Prenons désormais le cas où la largeur de l’écran est inférieure de 640px, avec une densité de pixels de 2. Nous avons alors largeur de l'image à afficher >= 100% × largeur de l'écran × 2 ce qui donne largeur de l'écran <= 1/2 × largeur de l'image à afficher. On affichera donc l’image de 320px jusqu’à 160px (10em), l’image de 640px jusqu’à 320px et l’image de 1024px jusqu’à 640px (la limite haute du cas étudié).

Prenons enfin le cas où la largeur de l’écran est **supérieure à 640px et la densité de 2. Nous avons alors largeur de l'image à afficher >= 1/3 × largeur de l'écran × 2, donc largeur de l'écran <= 3/2 × largeur de l'image à afficher. L’image de 320px ne servira pas (car 480px < 640px), celle de 640px servira jusqu’à 960px (60em) et celle de 1024 au delà.

Comme vous le voyez, tous les cas doivent être calculés et on aboutit parfois à des résultats peu intuitifs. Par exemple, ici, nous avons 5 points de rupture : 160px, 320px, 640px, 960px et 1920px et 9 media queries à écrire !

@media screen and (min-width: 20em) and (max-width: 39.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/640.jpg);
  }
}
@media screen and (min-width: 40em) and (max-width: 59.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/320.jpg);
  }
}
@media screen and (min-width: 60em) and (max-width: 119.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/640.jpg);
  }
}
@media screen and (min-width: 120em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/1024.jpg);
  }
}
@media screen and (max-device-pixel-ratio: 2) and (max-width: 9.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/320.jpg);
  }
}
@media screen and (max-device-pixel-ratio: 2) and (min-width: 10em) and (max-width: 19.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/640.jpg);
  }
}
@media screen and (max-device-pixel-ratio: 2) and (min-width: 20em) and (max-width: 39.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/1024.jpg);
  }
}
@media screen and (max-device-pixel-ratio: 2) and (min-width: 40em) and (max-width: 59.99em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/640.jpg);
  }
}
@media screen and (max-device-pixel-ratio: 2) and (min-width: 60em) {
  .resp-img {
    background-image: url(http://traindrop.github.io/fiches/006/assets/1024.jpg);
  }
}

Acte 3

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

Comment en tirer partie ?

Il n’existe pas vraiment, pour l’instant, de solution basée uniquement sur CSS. Le plus simple est d’utiliser la librairie de détection Modernizr qui s’occupera de poser sur l’élément html la class webp si ce format est supporté. La solution consiste donc à dupliquer l’ensemble du code où nous définisions les images de fond pour préfixer les règles par .webp , et à charger un script de détection dédié. Mais attention, le script est asynchrone : cela veut dire que la classe webp ne sera pas posée immédiatement. Si on ne fait rien, les images JPG seront chargées en premier avant d’être remplacées par le Webp, ce qui n’est pas optimal. Solution : préfixé l’ensemble des règles définissant les fonds JPG par no-webp, la classe posée sur webp n’est pas supporté. Voir l’exemple.