TD3 Mise en page : Posons le cadre.

Veuillez activer Javascript.

There is a lesson here for ambitious system architects : the most dangerous enemy of a better solution is an existing codebase that is just good enough.

Eric S. Raymond

Le modèle de boîte

Disclaimer :

Et oui ! Comme dans le TD précédent, nous commencerons par un peu d'histoire et de théorie.

Informaticien != BruteEpaisseQuiNePenseQuACoder, alors merci de prendre le temps de lire et surtout de comprendre. ;)

Les technologies changent sans cesse, mais les concepts demeurent.

La minute historique

Pour comprendre pourquoi la mise en page en CSS tient parfois du casse-tête ou de l'usine à gaz, remontons presque 30 ans en arrière.

À l'origine, HTML a été pensé comme un langage de présentation de texte.

Bien qu'il ait beaucoup évolué et qu'il soit désormais courant d'utiliser HTML pour créer des interfaces d'applications web, ses principes fondamentaux de mises en page sont restés ceux d'un traitement de texte.

La complexité de la mise en page sous CSS tient donc au fait qu'au fil des années, l'usage de HTML+CSS a été étendu à des domaines de plus en plus diversifiés :

Cette extension progressive des usages s'est faite en essayant de conserver à la fois une compatibilité ascendante et surtout descendante :

Au passage, cette problématique de rétrocompatibilité est une question récurrente lorsque l'on fait du développement informatique.

Malgré d'innombrables évolutions ces dernières années, le couple HTML/CSS reste donc fondamentalement conçu sur les mêmes principes qu'un traitement de texte ; il convient donc d'expliciter brièvement ceux-ci.

Principes de base d'un traitement de texte

En 1968, Donald Knuth, informaticien et mathématicien de renom, publie le volume I de The Art of Computer Programming, qui reste un ouvrage de référence en algorithmique de nos jours. À l'époque, la mise en page se fait encore à la main, à l'ancienne. Mais en 1976, lors de la première réédition, la mise en page se fait désormais par ordinateur, de manière automatisée... et Donald Knuth n'est pas satisfait du rendu.

Il se penche alors sur la question des algorithmes permettant de mettre en page automatiquement un texte, tout en conservant la même qualité de rendu que celle obtenue manuellement par les typographes autrefois.

Les algorithmes qu'il a écrit et l'implémentation de référence, TeX, sont encore utilisés aujourd'hui.

La notion fondamentale d'un traitement de texte (LaTeX par exemple) est la boîte.

Ces boîtes sont ensuite agencées selon deux axes, orientés selon le sens d'écriture :

Ces axes varient notamment selon la langue utilisée :

Dans tout ce qui suit, pour simplifier notre propos, nous nous placerons dans le cadre des langues européennes ; en particulier nous considèrerons que l'axe principal est horizontal (ce qui est vrai pour la grande majorité des langues).

Axe principal et axe transversal

Considérons par exemple la phrase suivante : Se coucher tard nuit, dixit Raymond Devos.

  1. Le logiciel de traitement de texte, ou le moteur de rendu HTML, voit le caractère S (on parle de glyphe en typographie). Il va créer une boîte rectangulaire le contenant.
  2. Il crée de même une boîte pour e, qu'il va coller à la suite selon l'axe principal.
  3. Comme le caractère suivant est un espace, nous sommes à la fin d'un mot. Il va créer alors une boîte correspondant au mot Se, qui contiendra la boîte de S et celle de e.
  4. Le logiciel regarde alors si il reste assez de place dans la ligne :
    • si oui, comme ici, il rajoute la boîte Se à la fin de la ligne ;
    • sinon, il crée une boîte correspondant à la ligne, et la place sur la page. Il se déplace selon l'axe secondaire en ajoutant l'interligne, et se prépare à commencer une nouvelle ligne.
Boîtes dans un traitement de texte

On voit ainsi que les boîtes se positionnent les unes à la suite des autres :

Bien évidemment, les lignes vont ensuite être regroupées en paragraphes, chaque paragraphe constituant lui aussi une boîte ; les paragraphes, eux aussi, se placeront les uns à la suite des autres selon un mode vertical.

Ce mécanisme de boîtes et de mode horizontal/vertical explicité, revenons maintenant à notre HTML/CSS.

Il y a cependant une différence notable, dès l'origine, entre un traitement de texte classique et un moteur de rendu HTML : c'est la notion de page. Quelle est la différence ? Quelles en sont les conséquences ? (Créez un fichier web/TD3/td3.html pour répondre).

Boîtes inline et boîtes block

Les deux types fondamentaux de boîtes

Le contenu d'une page HTML est constitué de textes, qui vont s'assembler en boîtes selon l'axe principal (horizontal), ou secondaire (vertical).

En particulier, tous les mots vont constituer des boîtes placées en mode horizontal.

Qu'en est-il des éléments de la page ?

(Rappel : nous appelons élément un nœud correspondant à une balise HTML, et le contenu qu'il engendre. Par exemple, un élément img correspond à une image, un élément p à un paragraphe.)

À quelques exceptions près (tableaux ou listes par exemple), tous les éléments HTML vont se répartir selon deux catégories :

Autrement dit, il y aura un saut de ligne avant et après chaque élément de type block, mais pas entre deux éléments de type inline successifs (sauf s'il n'y a plus de place sur la ligne).

Classification

Voici la classification des principaux éléments :

Éléments inline Éléments block

Balises de texte

b, i, em, strong, sup, sub

Titres

h1, h2, ..., h6

Images

img

Balises structurant la page

main, section, header, footer, nav, aside...

Liens

a

Paragraphes

p

Listes

ol, ul

Divers

cite, q, code...

Divers

blockquote, pre...

Balise neutre

span

Balise neutre

div

Les éléments li ne sont à proprement parler de type block, mais se comportent globalement de la même manière (au retrait près).

Les boîtes génériques

Il est parfois utiles de générer des boîtes qui n'ont pas de signification précise, mais servent simplement à appliquer un style à un morceau de texte ou à une partie de la page.

Les balises neutres div et span servent à construire de telles boîtes. Comme dit plus haut, la balise div construit une boîte de type block, et la balise span une balise de type inline.

			
    <div class="encadre">
        <p>Le drapeau grec est <span class="bleu">bleu</span> et <span class="blanc">blanc</span>.</p>
        <p>Il est constitué d'une croix blanche sur fond bleu et de neuf bandes blanches et bleues.</p>
    </div>
            
		
			
    .bleu {
        color: blue;
        font-weight: bold;
    }
    
    .blanc {
        color: white;
        font-weight: bold;
    }
    
    .encadre {
        background-color: blanchedalmond;
        border: solid burlywood 2px;
    }
        
		

Marges et bordures

Schéma général

Autour du contenu, les boîtes peuvent avoir :

Margin et padding

On modifie les dimensions d'une boîte à l'aide des propriétés width (largeur) et height (hauteur).

Si les dimensions sont données en pourcentage, elles se réfèrent à l'élément parent.

			
    <div id='A'>
        <div id='B'>

        </div>
    </div>        
    
		
			
    #A { 
        background-color: blue;
        /* 80% de la largeur du body */
        width: 80%;
        height: 120px;
    }

    #B {
        background-color:red;
        /* 20% de la largeur du bloc A */
        width: 20%;
        /* 50% de la hauteur du bloc B */
        height: 50%;
    }    
    
		

On peut également imposer des dimensions maximales et minimales à un objet.

Par exemple, on peut donner une taille en pourcentage, tout en imposant une taille minimale ou maximale en pixels. Cela permet d'adapter le rendu à la taille de l'écran dans une certaine limite.

On utilise pour cela les propriétés min-width, min-height, max-width et max-height.

Remarque

En cas de conflit, la propriété min-height l'emporte sur max-height, qui l'emporte elle-même sur height (idem pour min-width, max-width et width).

Modifiez votre fichier CSS pour que votre main fasse 80% de la largeur de la page, mais avec un minimum de 500px de large et un maximum de 1000px.

Vérifiez que ces contraintes sont respectées en modifiant la taille de la fenêtre du navigateur.

Boîtes inline

Pour une boîte de type inline (par exemple, un élément strong), la hauteur ne peut pas être modifiée manuellement.

Historiquement, les dimensions des boîtes CSS étaient celles du contenu, c'est-à-dire qu'elles n'incluaient ni la marge intérieure (padding), ni le cadre (border). Cette manière de faire complique considérablement la mise en page, obligeant à faire des calculs à chaque fois qu'on veut donner une dimension précise à une boîte.

Pour des raisons de rétrocompatibilité, c'est toujours le comportement par défaut, mais il est vivement conseillé de le modifier pour tous les éléments, en procédant comme suit :

Rajouter au début de votre fichier CSS la règle suivante :

				
    * {
        box-sizing: border-box;
    }
                
			

De cette manière, la marge intérieure et la bordure seront incluses dans les dimensions de tous les éléments de la page.

Modifier les marges

La marge intérieure correspond à la propriété padding et la marge extérieure à la propriété margin.

Il existe plusieurs syntaxes pour définir les marges intérieures ou extérieures :

			
/* Définition de la marge intérieure gauche en pixels */
padding-left: 20px;

/* Définition de la marge extérieure droite en pourcentage */
margin-right: 10%;

/* Définition de toutes les marges extérieures à la fois : top, right, bottom, left
(dans le sens des aiguilles d'une montre) */
margin: 20px 10px 15px 30px;            

/* On peut aussi donner la même valeur aux 4 marges (extérieures ici) : */
margin: 10px;

/* Ou donner une valeur aux marges extérieures du haut et du bas, 
   et une autre à celles de gauche et de droite : */
margin: 10px 30px;

/* (Même chose pour les marges intérieures bien sûr.) */
        
		
Boîtes inline

Une boîte de type inline (par exemple, un élément strong) ne peut pas avoir de marge extérieure en haut ou en bas.

Réglez l'espacement entre les paragraphes et leur retrait à votre convenance ; faites de même pour les listes.

Augmentez le retrait avant les titres h2, pour bien séparer les différents exercices.

Modifier les cadres

Voici les principales propriétés et valeur des cadres en CSS :

Propriété Valeurs (exemples) Description
border-style

none, solid, dotted, dashed, double...

Liste complète

Type de cadre
border-width 2px, 0.4em, 1% Épaisseur du cadre
border-color orange, #A4EF78, rgba(12,48, 35, 21) Couleur du cadre.
border-radius 10px, 1.2em, 3% Rayon des coins (cadres arrondis).

Pour chacune des ces propriétés, on peut notamment :

On peut utiliser également utiliser la propriété raccourcie border pour définir simultanément border-style, border-width et border-color.

Principales unités utilisables en CSS

Voici les principales unités utilisables en CSS :

Recommandé Usage occasionnel Non recommandé
Écran em, px, % ex pt, cm, mm, in, pc
Imprimante em, cm, mm, in, pt, pc, % px, ex

L'unité px (pixels) impose une taille absolue, tandis que les unités em et % donne une taille relative.

Plus d'informations sur le site du W3C.

On peut éventuellement combiner des unités, pour obtenir une mise en page dynamique mais conservant certaines contraintes, en utilisant la fonction calc() :

			
        width: calc(200px + 20%)
        
		

Les 4 opérations usuelles sont autorisées : +, -, * et /. Les opérations prioritaires sont bien sûr * et / (pensez aux parenthèses si besoin !).

Plus d'informations sur MDN web docs...

Attention aux espaces !

Dans calc(), les espaces autour des opérateurs + et - sont obligatoires ! Par exemple, calc(200px+20%) ne fonctionnera pas !

Centrage horizontal

Les boîtes de type block peuvent être centrées horizontalement dans leur conteneur en fixant les marges de gauche et de droite à auto.

			
    <div id="A">
    </div>
    <div id="B">
    </div> 
        
		
			
    #A {
        height: 30px;
        width: 200px;
        background-color: orange;
        /* On fixe la marge du haut et du bas à 20 pixels,
           et celles de gauche et de droite à auto. */
        margin: 20px auto;
    }
    
    #B {
        height: 80px;
        width: 50px;
        background-color: red;
        /* On fixe la marge du haut et du bas à 10 pixels,
        et celles de gauche et de droite à auto. */
        margin: 10px auto;
    }
    

		

Modifiez votre titre principal pour qu'il soit centré sur la page et prenne 80% de la largeur de la page, mais avec une marge minimale de gauche et de droite de 20 pixels.

(Astuce : vous pouvez utiliser la fonction calc().)

Changer de type de boîte

La propriété display

Nous avons vu que (presque) tous les éléments d'une page web étaient par défaut soit de type inline, soit de type block.

Cependant, ce comportement par défaut peut être changé, à l'aide de la propriété display.

En particulier, on préfère souvent afficher les images comme des blocs, et non en ligne. Cela permet notamment de centrer facilement une image horizontalement et de régler l'espacement avec le texte précédent et suivant.

			
        <p>Considérons l'extrait de code suivant :</p>
        
        <code>
        for (let caracter of [':', '!', '?', ';', "»"]) {
            node.textContent = node.textContent.replace(" " + caracter, " " + caracter);
        }
        </code>
        
		
			
    code {
        /* On change de modèle de boîte. */
        display: block;
        /* On centre la boîte. */
        margin: 20px auto;
        /* Cadre */
        border: solid 2px gray;
        /* Fond */
        background: beige;
        /* Affichage des espaces tels quels. */
        white-space: pre;
    }
        
		

Le type inline-block

Les boîtes de type inline offrent certaines limitations ; en particulier, la hauteur d'une telle boîte ne peut pas être modifiée manuellement.

Le type inline-block permet de créer des boîtes de type bloc — donc en particulier de hauteur librement modifiable — mais qui ne provoquent pas de retour à la ligne.

Autrement dit, une boîte inline-block est une boîte block fonctionnant en mode horizontal.

Résumé des différences entre les boîtes block, inline et inline-block :

inline inline-block block
mode horizontal horizontal vertical
largeur par défaut ajustée au contenu ajustée au contenu toute la largeur disponible
margin-top et margin-bottom modifiables non oui oui
hauteur modifiable non oui oui

Modifiez la page td3.html pour avoir en haut de la page un menu horizontal permettant d'accéder aux différents TD et à la page a_propos.html.

Vous devrez impérativement utilisez une liste pour le code HTML, et votre rendu devra ressembler à ceci :

menu

Les éléments du menu devront changer de couleur lors du survol par le pointeur de la souris.

Masquer une boîte avec display: none

Parmi les autres valeurs possibles de display, notons display: none qui permet de masquer un élément sans lui réserver de place. Combiné à la pseudo-classe :hover, cela permet de faire apparaître des éléments supplémentaires lorsqu'on survole certains éléments.

On peut aussi utiliser visibility: hidden ou opacity: 0, mais dans ce cas un emplacement est réservé pour l'élément caché.

Placement

Modes de positionnement

La propriété position précise par rapport à quoi l'élément va se positionner.

Les propriétés left, bottom, right et left permettent de préciser ensuite la position de l'objet par rapport à cette position de référence.

Voici les valeurs les plus courantes de position :

Les éléments dont la position n'est pas static s'affichent au dessus du reste de la page. Si plusieurs de ces éléments se chevauchent, c'est le dernier (dans le code HTML) qui sera au dessus.

Je suis une infobulle ! En utilisant les propriétés display et position et la pseudo-classe :hover, créez une classe infobulle dans votre fichier CSS. Utilisez là pour afficher un commentaire lorsque vous survolez les différents élements du menu de td3.html, ainsi que votre photo dans a_propos.html.

Éléments flottants

La propriété float (valeurs : left ou right) permet de sortir un objet du flux normal pour l'afficher à gauche ou à droite de la page. Elle n'a de sens que si la position est en mode static ou relative. Elle est utilisée en principe pour insérer des figures (image, tableau...) dans un texte, comme dans un article de journal.

On peut utiliser la propriété clear (valeurs : left, right ou both) pour qu'une boîte refuse qu'un élément flottant précédent déborde sur sa gauche ou sur sa droite.

float et clear

Dans la page web du TD1, faites « flotter » la photo du chien à droite des réponses, avec un légende en-dessous.

Couches successives

Empilement selon l'axe z

Lorsque deux boîtes se chevauchent, ce qui est possible lorsque les positions sont définies manuellement, il faut définir laquelle sera « au dessus » de l'autre.

On parle alors de z-order : la feuille étant en 2-D (axes x et y), on imagine que les éléments se déposent successivement dessus, formant un empilement en 3-D. L'ordre d'empilement, c'est-à-dire la position sur l'axe des z de chaque élément, détermine quel élément sera visible lorsque on regarde la feuille du dessus.

Empilement selon le z-order

Les éléments placés automatiquement (position: static) sont dessinés en premier, dans l'ordre dans lequel ils apparaissent dans le fichier HTML.

Ensuite, les éléments positionnés manuellement (relative, fixed, absolute...) sont dessinés par dessus (également dans leur ordre d'apparition en HTML).

Plus précisément, l'algorithme de dessin est le suivant :

Pour chaque élément, en partant du body, on dessine, dans l'ordre,

La propriété z-index

On peut manuellement changer l'ordre d'empilement (z-order) des éléments positionnés (relative, fixed, absolute...) en attribuant un z-index différent de 0.

La valeur de z-index doit être un entier relatif (elle vaut 0 par défaut). Plus elle est grande, plus l'élément sera dessiné tardivement et apparaîtra donc au dessus des autres.

Un exemple dynamique (passez la souris sur le carré orange) :

			
    <div id="A"></div>
    <div id="B"></div>


		
			
div {
  width:100px;
  height:100px;  
}

#A {
  background:orange;
  position:relative;
}

#A:hover {
  z-index:1;
}

#B {
  background:blue;
  position:relative;
  bottom:60px;
  left:40px;
}


		

Le contexte d'empilement

En réalité, c'est un peu plus complexe que cela : la propriété z-index indique qui est dessiné en premier entre deux éléments positionnés... à condition qu'ils appartiennent au même contexte d'empilement !

Les critères pour savoir si deux éléments appartiennent au même contexte d'empilement sont assez complexes.

En gros, un élément crée un nouveau contexte d'empilement quand position vaut fixed, ou alors quand il a un z-index non nul et que position vaut relative ou absolute, ou encore quand son opacité est inférieure à 1.

Voici un exemple tiré de MDN web docs.

On considère des éléments div organisés comme suit :

Exemple de z-index

On voit que les z-index de DIV #4, DIV #5 et DIV #6 servent uniquement à comparer ces trois DIV entre eux, car ils sont dans le même contexte d'empilement (généré par DIV #3).

De même, DIV #1, DIV #2 et DIV #3 qui sont dans le même contexte d'empilement (celui de leur racine) seront comparés entre eux par leur z-index.

Pourquoi DIV #1 est-il devant DIV #4, alors que son z-index est plus faible ?

Exercice récapitulatif ★★★

Modifiez le menu horizontal du TD3, de manière à obtenir un menu déroulant en CSS pur : au survol d'un onglet TD1, TD2 ou TD3, un menu déroulant doit apparaître en dessous proposant d'accéder directement aux différents exercices de chaque TD (c'est-à-dire aux différents titres h2).

(Résistez à la tentation de chercher des solutions toutes faites sur Stackoverflow !)

Version dépliée :

menu déroulant

Version repliée :

menu déroulant

Compléments sur les boîtes

Cette partie expose un certain nombre de subtilités de la mise en page avec CSS.

Si vous êtes en retard, ou si vous ne vous sentez pas très à l'aise avec CSS, passez directement à la partie suivante.

À l'inverse, si vous aviez déjà quelques connaissances en HTML/CSS, je vous conseille de la lire car elle détaille certains comportements méconnus et parfois surprenants de CSS.

Comportement interne et externe

Chaque mois, de nouvelles fonctionnalités sont ajoutées aux spécifications CSS, puis progressivement implémentées dans les navigateurs. Les propriétés CSS existant aujourd'hui sont innombrables et nous ne prétendrons pas en faire le tour, loin s'en faut.

Beaucoup de fonctionnalités ajoutées récemment concernent des usages particuliers (pour certaines langues par exemple) ou des fonctionnalités avancées (formes ou animations sophistiquées...).

À l'inverse, la refonte du système de mise en page de CSS, publiée par étapes depuis 2015 par le W3C, modifie en profondeur les techniques de mise en page en CSS.

Dans la prochaine section, nous évoquerons la mise en page via Flex et Grid, qui constituent à eux seuls une révolution dans le monde du web design.

Au delà de ces évolutions les plus spectaculaires, le W3C s'est efforcé de clarifier l'existant, et en particulier le rôle de la propriété display... tout en conservant une compatibilité avec l'existant. Pas simple !

Le W3C a ainsi proposé de scinder à terme la propriété display en deux sous-propriétés, display-inside et display-outside.

Cette séparation est encore loin d'être implémentée dans les navigateurs, mais elle aide déjà à comprendre le fonctionnement de display et en particulier les nouvelles valeurs de display qui ont été rajoutées récemment.

L'idée est que le comportement d'une boîte possède deux aspects.

Le comportement externe (display-outside)

Le comportement externe d'une boîte, c'est la manière dont elle interagit avec son environnement : son parent (appelé aussi conteneur) et ses frères.

Cette partie est relativement simple : il n'y a que deux comportement possibles, que nous avons déjà rencontrés.

Ces comportements par défaut peuvent cependant être modifiés par le parent, qui peut par exemple forcer tous ses enfants à se placer en mode horizontal (selon le comportement interne du parent).

Le comportement interne (display-inside)

Le comportement externe d'une boîte correspond à la manière dont elle gère ses enfants.

Si vous avez suivi, la disposition des enfants dépend à la fois du comportement interne du parent, et du comportement externe de chaque enfant.

Il existe 4 principaux comportements internes possibles :

(D'autres comportements spécifiques existent, comme table pour les tableaux, cell pour les cellules de tableau, etc.)

Voici les principales valeurs possibles de display, en fonction du comportement interne et externe souhaité pour la boîte :

Comportement interne
flow flow-root flex grid
Comportement
externe
block block flow-root flex grid
inline inline inline-block inline-flex inline-grid

Nous consacrerons la dernière partie de ce TD aux comportements internes flex et grid, qui permettent de contrôler assez finement l'affichage du contenu, mais auparavant nous allons revenir un peu sur comportement des boîtes, et expliquer la différence entre le comportement traditionnel flow et le comportement flow-root.

Vos boîtes sont-elles étanches ?

Réponse courte : non.

Dans un certain nombre de cas, le contenu d'une boîte peut sortir de celle-ci et/ou interagir avec l'extérieur.

Nous allons détailler quelques cas notables.

La fusion des marges

L'algorithme de mise en page des boîtes est assez compliqué lorsque l'on s'y penche de près, car en matière de CSS le diable est dans les détails...

La question des marges verticales en particulier est assez complexe.

Considérons par exemple le code HTML et CSS suivant :

			
<main>
    <div class="pere">
        <div class="fils">
        Ceci est un paragraphe.
        </div>
        <div class="fils">
        Ceci est un paragraphe.
        </div>
        <div class="fils">
        Ceci est un paragraphe.
        </div>
        <div class="fils">
        Ceci est un paragraphe.
        </div>
    </div>
</main>
        
		
			
body  {
  background:#ccc;
}

 /* On fixe toutes les marges à 10 pixels */

main {
  background:#aaf;
  margin:10px;
  }
  
.pere {
  background:#eaa;
  margin:10px;
  }
  
.fils {
  background:#ffc;
  margin:10px;
  }
  
  * {
  border: dashed thin;
}
        
		

Voici le résultat :

Enlevons maintenant juste les bordures, sans toucher aux marges.

Le résultat est surprenant ! Diantre ! Que s'est-il passé ?

			
* {
  border: dashed thin;
}
		

Le phénomène que nous avons mis en évidence s'appelle le Margin Collapse.

En effet, pour une mise en page plus harmonieuse entre paragraphes, les marges verticales fusionnent dans un certain nombre de cas :

Dans l'exemple précédent, que se passe-t-il si l'on supprime la bordure de main (en pointillés) ? Pourquoi ?

Gestion des débordements (overflow)

Par défaut, les boites essaient de s'adapter à leur contenu.

Ce n'est pourtant pas toujours possible.

Le problème se pose fréquemment quand la boîte a une taille imposée (via les propriétés height, width, max-height ou max-width)

Dans ce cas, il peut se produire un overflow, c'est-à-dire un débordement du contenu.

La propriété overflow permet de préciser le comportement en cas de débordement.

Ses valeurs sont :

On peut éventuellement spécifier un comportement différent selon que le débordement soit vertical ou horizontal (à l'aide overflow-x et overflow-y).

Mon float se fait la malle !

Le cas des éléments flottants est un peu particulier. Un élément flottant (float: left ou float: right) n'est en effet pas forcément géré par son parent direct, ce qui explique qu'il est fréquent que le flottant sorte du cadre de son parent (qui, ne le gérant pas, ignore en particulier ses dimensions).

Ce genre de problème se règle traditionnellement en utilisant de manière astucieuse la propriété clear vue précédemment (dans le jargon des webdesigners, c'est ce qu'on appelle le clearfix hack), mais cela peut vite être assez complexe.

Il existe désormais une méthode propre, supportée par les navigateurs récents, pour s'assurer que le float ne déborde pas. Elle est basée sur la notion de Block Formating Context.

Les blocs racines (Block Formatting Context)

On dit qu'un élément génère un Block Formatting Context lorsqu'il est intégralement responsable de son affichage, en particulier des flottants qu'il contient.

Pour simplifier, nous appellerons un tel élément un bloc racine.

Le bloc racine principal d'une page web est l'élément html.

Un certain nombre de propriétés, transforme automatiquement un élément en bloc racine ; citons notamment :

Sont aussi des éléments racines ceux qui possèdent une valeur de display autre que inline et block (cf. le tableau des valeurs possibles vu précédemment).

En particulier, on peut utiliser :

En rajoutant display: flow-root à l'élement main (fond violet), l'élément flottant reste dans la boîte :

Remarque

Les marges extérieures d'un élément racine ne fusionnent pas avec celles de ses enfants.

Les nouveaux modes de mise en page

Face aux évolution des utilisations de HTML/CSS, le W3C a publié ces dernières années un nouveau système de mise en page, tout en assurant bien sûr la rétrocompatibilité.

La propriété display possède désormais deux nouvelles valeurs, flex et grid, supportées depuis par les principaux navigateurs.

Jusqu'à récemment, en effet, les mises en pages sophistiquées étaient obtenues en détournant la propriété float de son utilisation première, ce qui posait tout un tas de problèmes, plus ou moins résolus par des hacks souvent assez obscurs, transformant rapidement tout design CSS un peu élaboré en véritable casse-tête chinois.

Cette époque est (presque) révolue !

Depuis fin 2015, tous les principaux navigateurs (sauf Internet Explorer hélas, mais sa part de marché fond comme neige au soleil) implémentent les boîtes flexibles (Flexbox), et depuis fin 2017 les grilles (Grid).

Sachant qu'il existe des livres de 120 pages consacrés exclusivement à Flexbox, vous imaginez bien que nous serons très très loin d'épuiser le sujet. Nota : ce n'est pas un conseil d'achat, je ne l'ai pas lu ! ;)

Pour autant, il serait vraiment dommage de ne pas en parler un minimum, car Flexbox et Grid simplifient les designs simples et rendent possibles des designs très élaborés.

Les boîtes flexibles

Les boîtes flexibles (ou Flexbox) permettent de disposer des éléments en ligne ou en colonne, en précisant de manière assez fine leur comportement.

Bien que Flexbox soit essentiellement un modèle de mise en page 1-D (contrairement à Grid qui est en 2-D), il est possible de construire des mises en pages complexes en imbriquant plusieurs Flexbox.

Le grand intérêt de Flexbox est que l'on peut définir le comportement des boîtes lorsque l'on redimensionne la page. Cela permet des mises en pages flexibles (d'où évidemment le nom), qui s'adaptent à la taille du support (jusqu'à un certain point — nous verrons d'autres techniques complémentaires dans un TD ultérieur).

Créer une Flexbox

Pour qu'un élément HTML devienne une Flexbox, c-à-d. gère son contenu de manière flexible, il faut lui donner la propriété display: flex (ou display: inline-flex si l'on veut qu'il soit de type inline).

Les enfants sont alors automatiquement de type flex-item.

Choisir sa direction

Il nous faut commencer par définir la direction principale de notre Flexbox.

On utilise la propriété flex-direction, qui peut prendre comme valeur :

(Ceci en supposant que l'axe principal de notre page est orienté de gauche à droite, et l'axe secondaire de haut en bas).

Alignement

Alignement selon l'axe principal

La propriété justify-content permet d'aligner les éléments selon l'axe principal.

En plus de pouvoir centrer les éléments ou les aligner d'un côté ou de l'autre, on peut aussi choisir par exemple d'espacer les enfants de manière régulière.

Attention ! Comme la direction de la Flexbox peut être changée, on n'utilise pas left ou right pour l'alignement d'un côté ou de l'autre, mais flex-start et flex-end.

Par défaut, justify-content vaut flex-start (ce qui signifie que les enfants sont alignés à gauche si la direction est row, en haut si la direction est column, etc.)

Les principales valeurs de justify-content sont :

Les mêmes valeurs en direction column :

D'autres valeurs sont possibles...

Alignement selon l'axe transverse (axe secondaire)

La propriété align-items permet d'aligner les éléments selon l'axe transverse.

Par défaut, align-items vaut stretch, ce qui signifie que tous les enfants sont étirés pour avoir la même hauteur (ou la même largeur si la direction est column).

Les principales valeurs de align-items sont :

Distribution de l'espace libre

La propriété flex permet de répartir automatiquement l'espace libre entre les enfants.

Attention ! Contrairement aux propriétés que nous avons vues précédemment, la propriété flex ne doit pas être donnée à la Flexbox elle-même, mais à ses enfants.

Considérons par exemple un parent contenant 3 enfants A, B et C.

			
<div class="parent">
    <div class="A">A</div>
    <div class="B">B</div>
    <div class="C">C</div>
</div>
            
		

On convertit le parent en Flexbox (display: flex), et on indique que le dernier enfant, C, doit grandir autant que possible (flex: 1).

			
/* 
L'élément parent est une Flexbox : 
*/
.parent {
  display: flex;
}

/* 
On fixe la taille initiale des enfants : 
*/
.A, .B, .C {
  width: 50px;
  height: 30px;
  }
    
/* 
L'élément C va grandir autant que possible
selon la direction principale de la Flexbox : 
*/
.C {
  flex: 1;  
  }
            
            

En ajoutant un peu de couleur, on obtient ceci :

Si l'on donne à un enfant une valeur de flex strictement positive, il va chercher à grandir en récupérant l'espace libre.

Si deux enfants A et B ont la même valeur strictement positive de flex, ils se partageront l'espace libre a égalité. Par contre, si A a pour valeur flex: 1 et B a pour valeur flex: 2, B récupèrera deux fois plus d'espace libre que A.

Bref, chaque enfant récupère une fraction de l'espace libre proportionnellement à sa valeur de flex.

Quoi d'autre ?

Il existe de nombreuses autres propriétés pour Flexbox dont nous n'avons pas parlé.

Si vous souhaitez aller plus loin dans vos expérimentations, voici une liste illustrée assez complète (en anglais) des propriétés Flexbox.

De manière générale, cependant, il vaut mieux réserver l'usage de Flexbox à des designs simples, et utiliser Grid, que nous allons aborder maintenant, pour des designs plus complexes.

Les deux se marient d'ailleurs très bien : c'est souvent une bonne idée d'utiliser une grille (Grid) à l'intérieur d'une boîte flexibles (Flexbox) (ou l'inverse).

Aide-mémoire

Les principales propriétés d'une Flexbox sont :

Les grilles (grid)

The next generation of CSS developers will laugh at us for not having used grids for layout before — learn the new spec and you’ll be laughing with them.

Chris Coyier (cofounder of CodePen)

Back to the futur

Avant le développement de CSS, les mises en pages un peu sophistiquées sur le web étaient faites avec des tableaux.

Avec la venue de CSS, le contenu, la structure et la mise en forme ont été complètement séparées (nous ne reviendrons pas sur le pourquoi de la chose, nous en avons suffisamment parlé auparavant).

Bien qu'assez rigide, et évidemment incompatible avec cette séparation structure/formatage, la mise en page en tableau permettait cependant de réaliser simplement des mises en page complexes en 2-D, respectant les alignements — choses qui tournent vite au casse-tête en CSS.

Les grilles CSS, disponibles depuis fin 2017, réunissent le meilleur des deux mondes : des alignements 2-D simples et précis, comme avec les tableaux de jadis, mais aussi toute la puissance et la flexibilité de CSS, et une mise en forme bien séparée de la structure du document.

Well done, old chap !

principe général

L'idée est qu'un conteneur de type grille est divisé en cases (comme dans un tableur). On place ensuite chaque enfant dans une case différente (un enfant peut éventuellement occuper plusieurs cases).

On transforme un élément en conteneur Grid en utilisant la propriété CSS display: grid (ou display: inline-grid si l'on veut un élément inline).

Les enfants d'un conteneur grid seront automatiquement de type grid-item, nous verrons plus loin les propriétés qu'on peut leur donner.

Définition des colonnes et des lignes

Il existe un grand nombre de manière de définir la grille elle-même, avec des syntaxes parfois un peu complexes.

Nous nous limiterons à quelques méthodes simples.

Dans cette partie, nous construirons explicitement la grille à l'aide des propriétés :

Dans ce premier exemple, nous avons 9 enfants à placer :

			
<div class="maGrille">
  <div class="A">A</div>
  <div class="B">B</div>
  <div class="C">C</div>
  <div class="D">D</div>
  <div class="E">E</div>
  <div class="F">F</div>
  <div class="G">G</div>
  <div class="H">H</div>
  <div class="I">I</div>
</div>
        
		

Nous définissons pour cela une grille de 3×3 :

			
.maGrille {
  /* affichage en grille */
  display:grid;
  /* 3 colonnes de largeur 100px */
  grid-template-columns: 100px 100px 100px ;
  /* 3 lignes de hauteur 50px */
  grid-template-rows: 50px 50px 50px ;
}

.maGrille>div {
  width: 50px;
  height: 20px;
  }
        
		

En rajoutant des bordures et un peu de couleur, on obtient ceci :

On remarque que la grille est remplie par défaut ligne par lignes.

Pour un remplissage colonne par colonne, on rajoutera la propriété grid-auto-flow: column;.

Lorsque le remplissage se fait ligne par ligne (respectivement colonne par colonne), la propriété grid-template-rows (respectivement grid-template-columns) est facultative.

Voici l'exemple précédent, mais avec cette fois-ci un placement automatique ligne par ligne sur 4 colonnes :

			
.maGrille {
  border: dashed cornflowerblue;  
  display:grid;
  grid-template-columns: auto auto  auto auto;
}
        
		

Le résultat :

On remarque que la grille occupe toute la largeur de la page. C'est qu'il faut bien garder à l'esprit que si le conteneur de type grid place ses enfants en grille, de l'extérieur il se comporte comme un élément block normal, qui cherche donc à occuper toute la largeur disponible.

Si l'on souhaite que les dimensions de la grille s'adaptent automatiquement au contenu, on peut par exemple l'inclure dans une Flexbox :

			
main {
  display: flex;
  border: solid thin;
  justify-content: center;
}
        
		

Il est possible de placer explicitement un enfant sur une case de coordonnées données, en utilisant les popriétés grid-column et grid-row. On numérote les lignes et colonnes en partant de 1.

Dans l'exemple précédent, en rajoutant le code CSS suivant :

			
  .G {
    grid-row: 3;
    grid-column: 2;
  }
        
		

on observe que l'enfant G s'est bien placé sur la 3e ligne, 2e colonne :

On peut indiquer qu'un élément s'étend sur plusieurs cases en indiquant la colonne de début et celle de fin (exclue) ; de même pour la ligne.

			
.G {
  /* G s'étend sur les colonnes 1-2-3 */
  grid-column: 1/4;
  /* et les lignes 2-3-4. */
  grid-row: 2/5;
  }
        
		

Pour que l'enfant G puisse s'étendre, on a aussi remplacé ses propriétés width et height par min-width et min-height.

Le résultat :

On remarque que les autres éléments sont automatiquement placés intelligemment sur la grille en évitant la zone réservée par G.

Définition de zones nommées

La définition de zone nommée est une manière très élégante de construire des grilles, en produisant un code CSS facile à relire.

On utilise pour cela la propriété grid-template-areas du conteneur :

			
.maGrille {
  border: dashed cornflowerblue;  
  display:grid;
  /* 
  On définit 4 zones nommées entete, main biblio et pied. 
  On voit qu'ici entete occupe 1 ligne et 4 colonnes, tandis
  que main occupe 3 lignes et 2 colonnes par exemple.
  Les cellules n'appartenant à aucune zone sont indiquées
  par un point.
  */
  grid-template-areas: "entete entete entete entete"
                       "main   main   .      biblio"
                       "main   main   .      biblio"
                       "main   main   .      .     "
                       "pied   pied   pied   pied  "
}
        
		

On applique ensuite la propriété grid-area à un enfant pour le placer dans l'une de ces zones nommées :

			
footer {
  grid-area: pied;
}        
        
		

Le résultat :

Espacement entre les cases

La propriété grid-gap permet de spécifier l'écart entre les cases.

On peut spécifier deux valeurs, la première pour l'espacement entre deux lignes, la deuxième pour l'espacement entre deux colonnes.

			
.maGrille { 
  display:grid;
  grid-template-columns: auto auto  auto auto;
/* Espacement entre les lignes, et entre les colonnes : */
  grid-gap: 10px 20px;
}
        
		

L'unité fr

L'unité fr (fraction d'espace) est une pseudo-unité : elle ne représente pas une longueur en soi, mais un coefficient servant à répartir l'espace libre disponible.

Par exemple, si une grille fait 800 pixels de large, et que la largeur des colonnes est 200px 1fr 2fr 1fr , l'espace libre restant est 800px-200px=600px . Ces 600 pixels seront répartis entre les 3 dernières colonnes. La somme des coefficients de ces 3 colonnes est 1+2+1=4 , donc la largeur de la colonne 2 (1fr) est 1/4×600px=150px , celle de la colonne 3 (2fr) est 2/4×600px=300px et enfin celle de la colonne 4 (1fr) est 1/4×600px=150px .

Ceci permet de construire facilement des grilles réactives, c'est-à-dire des grilles qui s'adaptent à la largeur du support (écran de smartphone...).

La même grille avec une largeur totale de 400 pixels :

Alignement

Pour simplifier, nous parlerons d'alignement horizontal pour l'alignement dans la direction principale, généralement horizontale, et d'alignement vertical pour d'alignement dans la direction transverse, généralement verticale.

Alignement horizontal

La propriété justify-items permet d'aligner horizontalement, et peut prendre les valeurs suivantes :

Un élément enfant peut personnaliser son alignement horizontal dans sa case à l'aide de la propriété justify-self.

Alignement vertical

La propriété align-items permet d'aligner verticalement, et peut prendre les valeurs suivantes :

Un élément enfant peut personnaliser son alignement vertical dans sa case à l'aide de la propriété align-self.

Autres propriétés

Si vous souhaitez aller plus loin dans vos expérimentations, voici une liste illustrée (en anglais) de toutes les propriétés Grid.

Aide-mémoire

Les principales propriétés d'un conteneur Grid sont :

Les principales propriétés de ses enfants sont :