Fork me on GitHub

Construire une grille fluide avec flexbox

Afficher/Masquer les propositions de solutions

Principes abordés : Responsive Web Design

Prérequis :

Acte 1

Nous allons construire ensemble une grille fluide, c'est-à-dire une CSS définissant une collection de classes qui nous permettront d'organiser, au sein d'un certain bloc, le contenu en colonnes et en gouttières. L'idée est de pouvoir très simplement définir que tel contenu occupe ½, ⅓, ⅙… de la largeur disponible, quelle que soit la largeur du viewport.

Notre grille n'occupera pas nécessairement tout l'espace, mais peut-être qu'une partie du site. Nous allons donc l'embarquer dans un élément de type bloc, un container, dont nous considérerons qu'il est "large à 100%", il sera la base de notre grille. Notez que si cet élément est le seul élément du body, alors tout se passera comme si la grille occupait toute la largeur disponible car, par défaut, cet élément de type bloc occupera véritablement toute la largeur.

Notre dernière contrainte sera d'utiliser le module Flexbox, introduit par CSS3 et compatible avec une majorité de navigateurs.

Nous allons partir du code HTML suivant :

<div class="grille">
    <!-- 100% large -->
</div>
Mettez en place deux colonnes de contenu. Le contenu principal occupe ⅔ de la largeur disponible, le contenu latéral, ⅓.
<div class="grille">
    <div class="col-2-3">
        <p>Contenu principal</p>
    </div>
    <div class="col-1-3">
        <p>Contenu latéral</p>
    </div>
</div>

Pour faire en sorte d'afficher les deux zones l'une à côté de l'autre, il faut commencer par définir le container comme un objet flexbox puis configurer ses options flex-direction et flex-wrap.

Il faut ensuite définir la largeur des colonnes. Vous utiliserez pour cela la syntaxe flex [1] :

flex: <'flex-grow'> <'flex-shrink'> <'flex-basis'> 

Lisez la documentation et expliquez la valeur à donner à chaque paramètre pour fortement maitriser la largeur d'un contenu par rapport à la largeur de son container

.

Proposition de solution :

.grille {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.col-2-3 {
  flex: 0 0 66.66%;
}

.col-1-3 {
  flex: 0 0 33.33%;
}

En mettant 0 comme valeur pour flex-grow et flex-shrink, on s'assure que l'élément ne s'adaptera pas ni à une largeur plus élevée (pour remplir) ni à une largeur plus faible (pour mieux s'insérer).

Acte 2

Si vous remplacez les contenus par du Lorem Ipsum [2], par exemple, vous allez vite vous rendre compte d'un problème : le texte d'un élément continue jusqu'au contenu suivant, ce qui n'est pas très agréable à l'oeil. Nous allons donc mettre en place des gouttières de 20px, qui permettront d'aérer le contenu.

Définissez des gouttières de 20px de large entre les contenus. Attention, les gouttières ne sont qu'entre les contenus… ni à gauche, ni à droite de la grille.

Vous rencontrez des problèmes pour faire rentrer vos contenus en largeur ? C'est normal, c'est à cause du modèle de boîtes par défaut. Pour simplifier la mise en place, appliquez à l'ensemble des éléments la règle box-sizing: border-box; [3] afin que la largeur définie d'un contenu soit véritablement sa largeur, indépendamment des padding et des border appliqués.

Description visuelle de l'impact de box-sizing: border-box;
Avec box-sizing: border-box; les bordures et les paddings sont dessinés à l'intérieur de la largeur de contenu définie. Les marges sont dessinées à l'extérieur. Original par Adam Kaplan
.

Proposition de solution pour la mise en place de box-sizing: border-box; [4]:

html {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
*, *:before, *:after {
    box-sizing: inherit;
}
        

Puis pour la mise en place des gouttières, nous pourrions utiliser last-of-type :

[class*='col-'] {
    padding-right: 20px;
}
[class*='col-']:last-of-type {
    padding-right: 0;
}

Cependant cela posera problème assez rapidement si nous avons des éléments d'une largeur suffisante pour passer à la ligne... car alors le last-of-type ne concernera pas les éléments situés à droite ou à gauche mais uniquement le dernier élément. Mieux vaut donc jouer avec la marge du container :

[class*='col-'] {
    padding-left: 20px;
}
.grille {
    margin-left: -20px;
}

Acte 3

Nous voudrions pouvoir nous laisser une option sur les gouttières extérieures, sous la forme d'une classe CSS supplémentaire que nous pourons utiliser, ou non, pour activer ces gouttières. Implémentez cette classe .grille-espace en CSS (n'oubliez pas de modifier votre HTML pour l'utiliser).
<div class="grille grille-espace">
    <div class="col-2-3">
        <p>Contenu principal</p>
    </div>
    <div class="col-1-3">
        <p>Contenu latéral</p>
    </div>
</div>
    

Ajoutons d'abord un padding latéral puis restorons la marge normale du container que nous avons supprimé précédemment :

.grille.grille-espace {
    margin-left: 0;
    padding-right: 20px;
}
        

Interlude

Nous avançons bien mais il est parfois difficile de voir où nous en sommes, faute de mise en forme.

Utilisez le code CSS suivant pour y voir plus clair (adaptez à votre code) :
body{
    background-color: #446CB3;
    color: #FFF;
}

.grille {
    border: 2px dashed rgba(255,255,255,0.3);
}

[class*='col-'] p {
    background: rgba(225,255,255,0.1);
    border: 2px solid rgba(255,255,255,0.2);
    display:block;
    padding: 1rem;
    margin: 1rem 0;
    font-size: 0.9rem;
    font-weight: 600;
}
    

Acte 4

Notre grille est bien partie, mais il faut désormais ajouter d'autres types de largeurs d'éléments pour rendre notre mise-en-page flexible aux besoins.

Ajoutez des classes pour mettre en forme des contenu occupant ½, ¼ ou ⅙ de la largeur disponible. Pensez aussi aux contenus occupant ⅔, ¾, ⅚… N'hésitez pas à modifier votre code HTML pour réaliser vos tests.

Essayez de mettre suffisament de contenu dans votre grille pour avoir au moins un, si c'est plusieurs passages à la ligne. Que se passe-t-il ? Que se passerait-il si la valeur flex-grow n'était pas à zéro ?

Proposition de solution :

.col-1-2 {
    flex: 0 0 50%;
}
.col-1-4 {
    flex: 0 0 25%;
}
.col-1-6 {
    flex: 0 0 16,66%;
}

Le nommage séparant les ½, ¼ ou ⅙ n'est en effet pas pertinent, car il existe alors plusieurs manières d'écrire la même chose. Par exemple, "la moitié de la largeur" peut être à la fois un demi, mais également trois sixièmes… C'est pour cette raison que vous trouverez de nombreuses grilles organisées sur le PGCD de 2, 3 et 6 : 12.

Acte 5

Appel de notre client: le deuxième contenu est en réalité un contenu à mettre en avant, alors que le premier contenu est, au contraire, à pousser en fin de grille…

Créez des classes CSS permettant la mise en avant ou en retrait d'un contenu.

La propriété order [5] est votre amie.

L'utilisation de order est extrêmement simple et n'a pas d'équivalent dans une autre méthode de création de grilles :

/* Contenu ramené vers l'avant */
.une {
    order: -1;
}
/* contenu poussé vers la fin */
.retrait {
    order: 99;
}

Voir le premier container de la démo.

Acte 6

Après divers essai, le résultat n'est pas jugé très satisfaisant : quand les largeurs sont trop petits, le texte ne rentre plus. Le client voudrait que les contenus aient une largeur minimale tout en faisant en sorte qu'ils passent automatiquement à la ligne si l'espace n'est plus suffisant. En revanche, il faudrait que les contenus exploitent l'ensemble de la largeur qui leur sera alors disponible.

Changer la façon dont vous définissez les largeurs de vos contenus pour définir, à la place, une largeur de 20em. En revanche, utilisez les paramètres de la propriété flex pour préciser que les contenus peuvent "s'aggrandir".

Notons qu'avec ce genre de modification, nous perdrons notre grille typographique. En revanche, nous gagnerons sur le ressenti utilisateur en terme de RWD sans pour autant avoir besoin de media queries.

La proprité suivante, appliquée sur l'ensemble des contenus, suffit. Elle explique à la fois la largeur minimum mais également la tendance à l'aggrandissement si nécessaire, pour exploiter au mieux la largeur disponible :

.min-col-20 { flex: 1 0 20em; }

Voir le deuxième container de la démo.