CSS3 : positionnement par grille (grid layout) et boites flexibles (flexbox)

Préambule

Le positionnement des boites a toujours été en programmation web un point délicat. Les développeurs web ont longtemps utilisé les tableaux HTML pour positionner textes et images.
Puis le CSS a proposé différentes techniques de positionnement via la propriété POSITION pouvant prendre les valeurs : fixe, absolu, relatif.
Mais beaucoup de développeurs WEB utilisaient la propriété float pour positionner des éléments les boites alors que c'est un mode de positionnement très limité et qui était initialement prévu pour disposer un élément inline (une image par exemple) dans un élément "block".

Nous avons vu par ailleurs qu'en transformant des boites en éléments "inline-block" celles-ci se positionnaient de front (et non plus les unes en dessous des autres) mais avec par défaut un alignement par rapport à la base ...

Excellent nouvelle : CSS3 introduit deux nouvelles spécifications : flexbox & grid layout.
L'outil flexbox (boites flexibles) est déjà bien implémenté sur les navigateurs.
Concernant l'outil "grid layout" (positionnement en grille), il est connu de la dernière version de MS Edge sortie en 2020.

Ces deux nouveaux outils se complètent parfaitement.
L'outi "grid-layout" permet de construire la structure générale de la page alors que l'outil "flexbox" n'est vraiment à l'aise que dans une seule direction ...
Pour prendre une exemple l'outil "grid layout" sera utilisé pour disposer les grandes divisions de la page (les boites header, nav, article, aside, footer) tandis que l'outil "flexbox" permettra de disposer les différents DIV contenus dans la division ARTICLE, par exemple.

Le positionnement en grille : outil "grid layout"

Je débute par l'outil "grid layout" (positionnement en grille).
Comme je disais plus haut, cet outil est implémenté par tous les navigateurs récents.

Une grille avec 6 cellules

Le code HTML

... <body> <div class ="boite"><h3>zone 1</h3></div> <div class ="boite"><h3>zone 2</h3></div> <div class ="boite"><h3>zone 3</h3></div> <div class ="boite"><h3>zone 4</h3></div> <div class ="boite"><h3>zone 5</h3></div> <div class ="boite"><h3>zone 6</h3></div> </body> ...

Aucune difficulté : 6 boites affectées de la même classe : "boite".

Le code CSS pour 2 lignes et 3 colonnes donc 6 cellules toutes identiques

	body {
		width : 90%;min-height : 100vh; border : 1px solid red; margin :auto;
		display : grid;
		/* 2 colonnes et 3 lignes donc 6 zones */
		grid-template-rows  : auto auto auto ; 
		grid-template-columns : auto auto ;
		grid-row-gap : 50px; 
		grid-column-gap : 20px; 
		}
	.boite {background : yellow; }
	h3 {text-align : center; }

Dans le cadre d'un responsive basique la page a une largeur de 90% de celle de la fenêtre.
La page a une hauteur minimale égale à la hauteur de l'écran (min-height : 100vh). En effet "vh" signifie "viewport height" donc la hauteur minimale de la page est égale à 100% de la hauteur du viewport.


Observez le rendu

Le premier DIV occupe implicitement la première cellule, le deuxième DIV la deuxième cellule et ainsi de suite.
Les trois colonnes ont la même largeur, les deux lignes ont la même hauteur donc les six cellules sont identiques !
Pour les que les lignes et les colonnes aient la même hauteur / largeur il suffit de leur attribuer la valeur "auto".

Version 2 : colonne du milieu deux fois plus large que la première et dernière

Le code HTML inchangé et le code CSS devient alors ...

	body {
		width : 90%;min-height : 100vh; border : 1px solid red; margin :auto;
		display : grid;
		/* trois colonnes et 2 lignes donc 6 zones */
		grid-template-columns : 1fr 2fr 1fr;
		grid-template-rows : 1fr 1fr;
		grid-gap : 30px 20px ;
		}
	.boite {background : pink ;  }
	h3 {text-align : center; }

Notez les valeurs des propriétés grid-template-rows & grid-template-columns !
Le raccourci grid-gap : esp. lig. esp. col. remplace avantageusement les propriétés grid-row-gap & grid-column-gap.
Observez le rendu

Comme les colonnes ne doivent pas avoir la même largeur, j'ai utilisé une nouvelle unité de mesure : "fr".
Pour les lignes j'ai aussi utilisé cette nouvelle unité de mesure à la place de "auto".

Organiser la structure générale de la page avec "display grid"

Donc le code HTM est simple :

<header><h3>En-tête de page</h3></header> <nav><h3>zone de navigation</h3></nav> <article><h3>Contenu de la page</h3></article> <footer><h3>Pied de page</h3></footer>

Quatre boites seulement !

Nous devons créer une grille de 2 colonnes et 3 lignes donc 6 cellules alors que nous n'avons que quatre divisions ...
Il n'est plus question d'une affectation implicite des boites aux cellules. L'affectation doit être explicite !
Pour l'affectation explicite des divisions aux différentes cellules de la grille nous avons trois solutions !

Première solution : fusion de cellules

Pour chaque division on précise la première cellule d'affectation et l'éventuelle fusion de cellules.

body {width : 90%;min-height : 100vh;  margin :auto;
		display : grid ;
		grid-template-rows: 1fr 7fr 2fr;
		grid-template-columns: 3fr 7fr; 
		grid-gap :20px 10px;
		}
		
	header { grid-row: 1;grid-column: 1  / span 2; }
	nav {grid-row: 2;grid-column: 1;}
	article {grid-row: 2; grid-column: 2;}
	footer { grid-row: 3; grid-column: 1  / span 2;}

header, nav, article, footer {border : 1px solid black ; }
h3 {text-align : center; }

On définit d'abord une grille de trois lignes et deux colonnes donc 6 cellules.
Chaque "fr" représente ici le dixième de la hauteur /largeur disponibles.

Observez le rendu

La valeur "span ..." a été ici associée à la propriété grid-column donc on fusionne (sur une même ligne) des colonnes.
Mais on peut aussi fusionner sur une même colonne des lignes ; il suffit d'associer la valeur "span ..." à la propriétégrid-row.

Deuxième solution : préciser les bornes

Pour chaque boite on précise les bornes de ligne et de colonne.
La grille comprend 2 colonnes et trois lignes. Donc les bornes de colonnes sont 1,2,3 (et non pas 0,1,2) et les bornes de lignes sont 1,2,3,4.
Ainsi la première ligne est entre les bornes de ligne 1 et 2 ; la dernière ligne est entre les bornes de ligne 3 et 4 ;
La première colonne est entre les bornes de colonne 1 et 2 ; la deuxième colonne est entre les bornes de colonnes : 2 et 3.
Extrait de la feuille de style :

...
	header {grid-row: 1 / 2 ; grid-column : 1 / 3 ; }
	nav {grid-row: 2 / 3 ; grid-column : 1 / 2 ;}
	article {grid-row: 2 /3 ; grid-column: 2 / 3 ; }
	footer {grid-row: 3 / 4; grid-column: 1 / 3; }
...

Le reste de la feuille de style est inchangé.

Pour chaque sélecteur il y a deux propriétés (grid-row & grid-colunm) et pour chaque propriété il y a deux valeurs séparées par une "/". La première valeur est la borne début et la deuxième valeur est la borne fin.
Ainsi il faut lire pour HEADER : bornes de ligne de 1 à 2 (donc première ligne) et bornes de colonne de 1 à 3 (donc deux colonnes).
Observez le rendu

Personnellement, j'aime pas trop cette technique.

Troisième solution : nommer les cellules

On nomme chaque cellule de la grille (propriété grid-template-area) ; plusieurs cellules pouvant porter le même nom. Puis on affecte à chaque division de la page un nom (propriétégrid-area)

C'est la technique que je préfère car elle me parait la plus claire au niveau du code CSS.
Je vous communique toute la feuille de style :

body {min-height : 100vh; width : 90%; margin :auto;
		display : grid ;
		grid-template-rows: 1fr 7fr 2fr;
		grid-template-columns: 3fr 7fr; 
		grid-template-areas: "h h"   "n a"  "f f";
		grid-gap :20px 10px;
 }

header {grid-area: h;}
nav {grid-area: n;}
article {grid-area:a;}
footer {grid-area:f;}

header, nav, article, footer {border : 1px solid black ; }
h3 {text-align : center; }

Apparition de la propriété grid-template-areas qui nomme les six cellules de la grille.
Les deux cellules de la première ligne se nomment "h" ; les cellules de la deuxième ligne se nomment respectivement "n" et "a" ; les deux cellules de la troisième ligne se nomment "f" ;

Apparition de la propriété grid-area qui affecte un nom de cellule à chaque division :

Les propriétés grid-row, grid-column disparaissent et sont remplacées par grid-area
Observez le rendu

Comme vous avez pu l'observer ces trois solutions donnent exactement le même rendu, et c'est heureux.
Donc à vous ce choisir la méthode qui vous convient et vous oubliez les deux autres ...
Notez bien que les propriétés qui commencent par "grid-template" sont utilisées au niveau du "grid-container" (BODY dans mes exemples). La propriété grid-area est utilisée au niveau de chaque grande division.

L'outil "grid layout" et le responsive design

En combinant les "media queries" et le positionnement en grille on peut obtenir un site responsive c'est à dire qui s'adapte à tous les écrans : de l'écran géant d'un ordinateur de bureau au minuscule écran d'un "ordiphone" (comme disent les québecois).

Structure générale de chaque page d'un site

Sur un petit écran la boite ASIDE est masquée ; chacune des quatre autres boites occupent toute la largeur de l'écran.

Le code de la partie BODY

Il est totalement indépendant de la taille de l'écran.

... <meta name="viewport" content= "width=device-width, user-scalable=yes" /> ... <header><h3>boite header</h3></header> <nav><h3>boite nav</h3></nav> <article><h3>boite article</h3></article> <aside><h3>boite aside</h3></aside> <footer><h3>boite footer</h3></footer> ...

N'oubliez pas la balise meta viewport dans la partie HEAD!

La feuille de style

body {
  min-height : 100vh; width : 800px; margin :auto;
  display: grid;
  grid-template-columns: 2fr 6fr 2fr; 
  grid-template-rows: 1fr 1fr 7fr 1fr; 
  grid-template-areas: "h h h"   "n n n "  "a a s"  "f f f";
  grid-gap : 10px;
 }
nav {grid-area: n;}
article {grid-area: a;}
aside {grid-area: s; }
footer {grid-area: f;}
header, nav, article, aside, footer {border : 1px solid black ;}
h3 {text-align : center; }

@media (max-width: 800px) 
{
	body { width : 100%; 	grid-template-areas: "h h h "   "n n n"   "a a a"   "f f f";   }
	aside {display : none; }
} 

L'espacement entre lignes et colonnes est le même : 10px.

Par défaut la largeur de BODY est égale à 800px mais si largeur de la fenêtre est inférieure à 801px alors la largeur de la page = 100% de la fenêtre. et la boite ASIDE est masquée (display : none).
Chaque grande division occupe une ligne de la grille !
Observez le rendu sur un PC puis réduisez la fenêtre pour simuler l'affichage sur un petit écran

L'outil flex : les boites flexibles

Lorsqu'on travaille avec l'outil flexbox deux axes interviennent : l'axe principal et l'axe secondaire.
L'axe principal est défini par la propriété flex-direction et l'axe secondaire est alors l'axe qui lui est perpendiculaire.
La valeur par défaut de flex-direction est "row" (donc axe principal par défaut = axe horizontal et axe secondaire = axe vertical).

Le célèbre framework CSS Bootstrap dans sa version 4, utilise beaucoup l'outil flexbox (et non pas grid-layout) même si la documentation officielle évoque le positionnement en grille dans sa traduction.

Positionnement flexible : exemple 1

Le code de la page

... <style> body {min-height : 100vh; border : 1px solid red; width : 100% ; } @media (min-width : 1000px) {body {width : 900px ; margin : auto ; } } section {display : flex; flex-direction : row;} div#boite1 {background : lime ; } div#boite2 {background : pink ; } div#boite3 {background : coral ; } p{margin : 10px ; font-size : 14pt; text-align : left;} h1 {text-align : center; font-size : 20px; } </style> </head> <body> <h1>Trois boîtes flexibles disposées selon un axe horizontal</h1> <section> <div id="boite1"> <p>Post hanc adclinis Libano monti Phoenice, ... </div> <div id="boite2"> <p>Et quoniam perasset... </div> <div id="boite3"> <p>Nihil morati post haec militares ... </div> </section> ...

Le conteneur SECTION contient trois DIV.
Notez que la largeur par défaut de BODY est de 100% de la fenêtre sauf si celle-ci fait au moins 1000px (BODY fait alors 900px de large avec centrage).
Notez la règle de style relative à l'élément SECTION : "display : flex; flex-direction : row;"
Ce qui signifie que SECTION est le "flex container" et que les enfants de SECTION seront des "flex-items".
Notez que pour les trois "flex-items" je n'ai défini ni hauteur ni largeur !
Observez le rendu sur un PC puis réduisez la fenêtre pour simuler l'affichage sur un petit écran

Les trois "flex-items" sont toujours sur la même ligne. Les différents items n'ont pas la même largeur ; Largeur en fonction du contenu. Les trois items occupent toute la largeur disponible.

Exemple 2 : l'axe principal est l'axe vertical

Le code de la page est strictement identique au précédent sauf dans la règle de style relative à SECTION : "display : flex; flex-direction : column;"
Donc cette fois l'axe principal est l'axe vertical !
Observez le rendu sur un PC ; réduisez la fenêtre pour simuler l'affichage sur un petit écran

Les trois "flex-items" sont toujours sur la même colonne. La hauteur de chaque item est fonction du son contenu et des dimensions du conteneur. Chaque item occupe toute la largeur disponible.

Exemple 3

Cette fois je fixe pour chaque "flex-item" une largeur. Règle de style nouvelle : div {width : 250px ; margin : 10px ; box-sizing : border-box; }
Axe principal : axe horizontal.
Testez le rendu

Sur un grand écran les trois items sont alignés à gauche sur l'axe horizontal ; ce qui n'est pas très joli.
Sur un petit écran le "width" de 250px n'est pas respecté !

La solution

Il faut rendre possible un saut de ligne. Il faut modifier la règle de style relative au flex-container :
section {display : flex; flex-direction : row; flex-wrap : wrap; }
Il y a donc une nouvelle propriété flex-wrap avec la valeur "wrap". Ce qui veut dire que les items peuvent passer sur une nouvelle ligne si c'est nécessaire. Bien sûr, si l'axe principal est l'axe vertical il s'agira d'un saut de colonne.
Valeur par défaut de cette propriété : "nowrap" qui veut dire "saut de ligne impossible".
Le rendu ; réduisez la fenêtre pour simuler l'affichage sur un petit écran

Sur un petit écran, le navigateur tient compte du "width : 250px". Le troisième item passe à la ligne puis le deuxième si la fenêtre est encore plus étroite. Le problème : les items toujours alignés à gauche ...

Gérer l'alignement des items selon les deux axes

La propriété justify-content gère l'alignement selon l'axe principal et la propriété align-items gère l'alignement selon l'axe secondaire.

justify-content

align-items

Exemple

Le "flex-container" contient six "flex-items".
La répartition des items dans le conteneur doit être équilibrée.

Le code (extrait) :

... <style> body {min-height : 100vh; border : 1px solid red; width : 100% ; } @media (min-width : 1400px) {body {width : 1300px ; margin : auto ; } } section {display : flex; flex-direction : row; flex-wrap : wrap; justify-content : space-around; } div {width : 200px ; background : lime ; margin-top : 5px; } p{margin : 10px ; font-size : 14pt; text-align : left;} h1 {text-align : center; font-size : 20px; } </style> </head> <body> <h1>Six boîtes flexibles disposées selon un axe horizontal <br>Saut de ligne possible si le conteneur n'est plus assez large ! </h1> <section> <div> <p>Post hanc ... </div> <div> <p>Nihil morati ... </div> ...

La page fait 1300px de large ou 100%.
Il y a en fait six DIV contenu dans le conteneur SECTION.
Notez bien la nouveauté dans la règle de style de SECTION : ...justify-content : space-around;
Testez ce code ; réduisez progressivement la fenêtre.

Selon la largeur de SECTION les six boites sont de front ou sur deux lignes voire sur 3 lignes avec à chaque des marges entre les items et les extrémités.

Positionner une boite flexible en haut à gauche du flex-container

Nous voulons qu'un item unique soit positionné en haut et à droite dans son conteneur.

Le code de la partie BODY

<div id ="parent"> <div id ="enfant"> <p>Post hanc adclinis Libano monti ... </div> </div>

Code CSS

	#parent{ display : flex;   flex-direction : column; 
			justify-content : flex-start ; align-items : flex-end; 
			border : 1px solid red; height : 600px ;  width : 600px ; margin : auto;}
	#enfant {background : pink ; width : 60% ; margin:10px ;  } 

La propriété justify-content gère l'alignement sur l'axe principal (ici l'axe vertical puisque flex-direction vaut "column")
La propriété align-items gère l'alignement sur l'axe secondaire (ici l'axe horizontal).
Le rendu !

Le "flex-item" est bien positionnée en haut et à gauche de son flex-container ("parent").

Exercice

Le centrage d'une boite flexible dans son conteneur

En CSS2 le centrage horizontal et vertical d'une boite dans son conteneur était un vrai casse-tête.
Avec l'outil flexbox le centrage d'une boite dans son conteneur devient un jeu d'enfant.

Le code de la partie BODY

La feuille de style

	#parent{ display : flex; justify-content : center ; align-items : center; 
			border : 1px solid red; height : 600px; width : 600px ; margin : auto;}
	#enfant {background : pink ; width : 60% ;  } 
	div p{margin : 10px ; font-size : 14pt; text-align : justify; }

Donc pour centrer selon les deux axes il suffit que ces deux propriétés justify-content & align-items aient la valeur "center".
Observez le résultat !

Centrer plusieurs boites flexibles

Code HTML

<div id ="parent"> <div class ="enfant"> <p>Post hanc adclinis Libano monti Phoenice, ... </div> <div class ="enfant"> <p>Post hanc adclinis Libano monti Phoenice, ... <p>Post hanc adclinis Libano monti Phoenice, ... </div> </div> ...

La feuille de style

Inutile de se prendre la tête !

#parent{ display : flex;   flex-direction : column; justify-content : center ; align-items : center; 
			border : 1px solid red; height : 600px ; width : 600px ; margin : auto;}
.enfant {background : pink ; width : 60% ; margin-top : 10px; } 
div p{margin : 10px ; font-size : 14pt; text-align : justify; }

Quid des anciennes techniques de positionnement.

La propriété float

Pour positionner une image ou une lettrine en haut à gauche ou à droite d'une boite avec le texte tout autour, continuez d'utiliser float.
Par contre cette propriété doit être bannie pour positionner les grandes divisions de la page ; l'outil grid-layout est plus approprié.

Position absolute

Pour supersposer des blocs vous serez toujours obligé d'utiliser la propriété "position" avec la valeur "absolute" et conjointement avec "z-index".
Par contre pour centrer verticalement un item dans son conteneur l'outil flexbox est plus approprié que le "bidouillage" avec le positionnement absolu.

Position fixe

Un menu ancré par rapport à la fenêtre c'est pratique; alors position : fixed & z-index sont encore incontournables !

Position relative

Je ne vois plus qu'un seul intérêt à position relative affecté à un conteneur : les positions en absolu des "enfants" par rapport à leur conteneur et non pas rapport au coin haut gauche de la fenêtre.

Display inline-block

Cette valeur de display reste utile pour disposer de front plusieurs images.
Par contre pour des blocs de texte il faut plutôt recourir à l'outil flexbox.
Retour menu