CSS : Motivations et principes
Motivations
Comme nous l'avons vu à la fin du cours sur le langage HTML, les éléments de mise en page (comme FONT par exemple) sont exclus du langage html5.
Il s'agit d'un paradigme général consistant à dissocier le contenu d'un document de sa forme. Dans le cas d'une page Web, le langage HTML est destiné à structurer et à décrire sémantiquement le contenu. Les feuilles de style CSS, dont nous allons discuter dans la suite, servent elles à spécifier la forme du document.
Les intérêts de cette séparation sont multiples :
- un style, appliqué à toutes les pages d'un site, assure la cohérence graphique de ce site ;
- la maintenance d'un site en est facilitée, un changement de charte graphique ne portera que sur la feuille de style et la modification sera propagée à toutes les pages ;
- plusieurs mises en forme différentes peuvent être proposées pour un même document sans que celui-ci ne soit répliqué en autant d'exemplaires ;
- le code HTML est lisible et facilement modifiable, sans que l'on ait à penser en même temps au contenu et à son apparence ;
- les pages, dégagées des balises de mise en forme, sont moins lourdes, donc plus rapides à circuler sur un réseau (la feuille de style est, le plus souvent, légère et ne sera chargée qu'une seule fois pour l'ensemble des documents qui l'utilisent) ;
- les balises donnent une indication sur la nature du contenu et cette indication pourra être utilisée par un moteur de recherche ou, plus généralement, par tout outil d'extraction d'information.
La solution retenue pour décrire des mises en forme se nomme donc feuilles de style ou, en anglais, Cascading Style Sheets (CSS). « En cascade » signifie que sont prises en compte les feuilles du navigateur, de l'utilisateur, puis de l'auteur du document. Les CSS peuvent être rapprochées des styles et des modèles de document des traitements de texte.
Enfin, précisons que les CSS que nous allons associer à des documents HTML, peuvent aussi être liées à des documents XML. Ces derniers deviennent alors visualisables sur des navigateurs Web. C'est une raison supplémentaire pour maîtriser le langage CSS.
Application d'un style à un code HTML
Dans une balise ouvrante, avec l'attribut STYLE
<EM STYLE="color:silver;">texte en argenté</EM>
Le style ne vaut alors que pour le contenu balisé.
Dans l'en-tête du document HTML
<STYLE>
/* commentaire : ici des définitions de style */
</STYLE>
Les styles définis à cet endroit sont valables pour tout le contenu du document.
Fichier CSS externe
Les styles sont définis dans un fichier texte avec l'extension .css, sans utiliser de balise STYLE (ce fichier ne contient pas de HTML).
Puis dans l'en-tête du document HTML, on fait le lien avec ce fichier CSS :
Cette fois, les styles sont communs à tous les documents HTML qui incluront le fichier CSS de cette manière. Si les deux premières méthodes peuvent être utiles, c'est bien la troisième qu'il faut privilégier sur un site Web.
Notion de boîte
La mise en page à l'aide de CSS repose sur la notion de boîte : il faut imaginer que tout contenu entre une balise ouvrante et une balise fermante est en fait dans une boîte. À l'aide des feuilles de style, on va pouvoir spécifier l'apparence du contenu de la boîte (color, font, background, etc.), les espacements à l'extérieur de la boîte (margin), les espacements à l'intérieur (padding), les bords (border), etc.
Naturellement, l'imbrication des balises implique une imbrication des boîtes. Ainsi, les éléments <LI> sont vus comme des boîtes contenues dans une boîte représentant la balise <UL>. Et tout le document est constitué de boîtes imbriquées dans la boîte <BODY>.
Il y a deux natures majeures et distinctes pour ces « boîtes » : block et inline. Les boîtes de type block prennent toute la largeur disponible et se placent les unes sous les autres. Les boîtes de type inline ne prennent que la largeur nécessaire et se placent l'une à côté de l'autre. Le type d'une boîte peut être changée à travers la propriété display. Nous verrons d'autres valeurs possibles et les comportements associés en temps utile.
Unités de mesure
Différentes propriétés, comme les marges par exemple, nécessitent de donner une mesure. Pour exprimer une mesure, plusieurs unités sont disponibles en CSS.
Il y a des mesures absolues et habituelles comme : px, pt, pc, mm, cm et in.
Sont également disponibles des mesures relatives à la police actuellement utilisée : em (hauteur des majuscules dans le contexte actuel), rem (hauteur des majuscules à la racine du document), en (hauteur des minuscules), etc. Ou une unité relative à la largeur disponible dans la boîte actuelle avec %.
Enfin, des unités de mesure sont relatives au matériel utilisé par le visiteur, on parle d'unités viewport. Le mot viewport désigne la partie du navigateur qui affiche le document et sur laquelle la CSS intervient. vw et vh s'expriment en pourcentage des dimensions de cette surface, largeur et hauteur.
L'objectif à poursuivre est de s'adapter aux préférences de l'utilisateur et à son matériel. L'utilisation des unités relatives participe à cet objectif en garantissant une mise en forme fluide, adaptative, du document.
Dans cet esprit, on ne spécifiera pas toujours de dimensions : on pourra simplement demander à un objet de s'adapter à la boîte dans laquelle il se trouve.
Notons enfin que le langage CSS nous permet de faire des calculs avec la fonction calc, en particulier au moment de spécifier une mesure.
syntaxe générale
Dans ce cours, nous distinguons trois temps dans le stylage d'un élément :
- désignation/repérage de l'élément dans la structure html,
- association des propriétés de mise en forme à cet élément,
- placement de la boîte dans le rendu final du document.
La combinaison sélection et propriétés prend la forme suivante en CSS :
sélecteur {
propriété-1: valeur-1;
propriété-2: valeur-2;
/* commentaire : ici suite éventuelle des propriétés */
}
Si les lignes propriété/valeur à l'intérieur des accolades sont assez simples, l'expression des sélecteurs est elle plus sophistiquée.
Sélection/repérage des éléments
Le langage de requêtes utilisé participe à la puissance des CSS (on pourra penser à XPath comme langage comparable).
Sélecteurs de types
C'est le cas le plus simple : on ne précise que le nom de l'élément concerné par la définition du style. Il est possible de faire partager le style par plusieurs balises (on sépare les noms des éléments par des virgules).
/* marge interne au document de 5 millimètres */ BODY { padding: 5mm; } /* le texte des paragraphes apparaîtra en bleu */ P { color: blue; } /* les titres sont en blanc sur fond bleu */ H1, H2, H3 { color: white; background-color: blue; }
Premiers exemples mettant en œuvre les mesures relatives vues à la section précédente.
/* le document occupe au moins toute la hauteur, plus si nécessaire */ body { min-height: 100vh; padding: 2em; } /* on interdit aux images de déborder de la boîte parente */ img { max-width: 100%; } /* taille des images qui s'adapte pour remplir l'élément-parent*/ img { object-fit: cover; } /* calcul de largeur */ section { width: calc(50% - 2em); }
Sélecteurs de classe ou d'identifiant
Il est également possible de proposer des apparences différentes pour un même élément. Nous avons précédemment décrit le style associé de manière générale aux balises P. Nous allons maintenant définir un autre style pour certains paragraphes dits ecolo. Cela passe par une distinction dans le code HTML : on va préciser la classe des paragraphes concernés.
<P CLASS="ecolo"> un paragraphe... </P>
Puis, on utilisera la syntaxe suivante pour décrire les caractéristiques de ces paragraphes particuliers :
P.ecolo { color: green; }
Plus généralement, on peut spécifier l'apparence des éléments d'une classe, sans préciser d'élément en particulier :
.ecolo { color: green; }
Le style vaut alors pour les éléments portant cette classe. Ici, cela aura pour effet de faire apparaître en vert tous les textes contenus dans balises de classe ecolo :
<H1 CLASS="ecolo">Titre militant</H1> <P CLASS="ecolo"> un paragraphe qui parle d'écologie... </P>
Enfin, il est possible de ne donner un style que pour une apparition unique d'un élément donné. Pour cela, il faut distinguer la balise ouvrante correspondant en lui donnant un identifiant unique :
<P ID="intro"> bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla introductif </P>
Puis définir le style du paragraphe nommé intro en utilisant l'une des syntaxes suivantes :
P#intro { color: blue; } #intro { color: blue; }
L'unicité d'un identifiant ne vaut que dans un même document. Ainsi, chaque document de notre site Web peut avoir (ou non) une intro, mais pas plus d'une, et toutes apparaîtront de la même manière.
Sélecteurs d'attributs
Ce sélecteur permet de tester la valeur d'un attribut et d'adapter le style en conséquence. On peut simplement tester la présence d'un attribut, ou tester sa valeur (de manière exacte ou approchée).
/* liens avec un attribut title */ a[title] { background-color: white; } /* paragraphes en anglais */ p[lang="en"] { color: red; } /* liens pointant vers une page de l'université */ a[href|="https://www.univ-lille.fr"] { border: solid 1px black; }
Sélecteurs par contexte
Ce type de sélecteurs permet de repérer des éléments selon leur entourage dans l'arbre html.
Il est possible de repérer un élément selon son ascendance :
UL LI { font-size: small}
/* stylage des items li descendants eux-mêmes d'un item li */
UL LI LI { font-size: x_small}
Ou de styler plus spécifiquement un descendant direct :
/* stylage des titres h1 fils directs d'une section */
section > h1 { color: red; }
Enfin, on peut repérer un élément selon son frère précédent.
/* stylage des paragraphes qui suivent directement un titre */
h1 + p { font-size: 120%; }
Par pseudo-classes
Les pseudo-classes permettent de repérer et de styler certains contenus déjà balisés, comme si nous leur rajoutions une classe.
Pseudo-classes qui caractérisent l'état dans lequel se trouvent les éléments visés.
/* style des liens selon leur état */ A:link A:hover /* passage de la souris sur le lien */ A:active A:visited /* :hover : passage de la souris sur un élément quelconque */ p.ecolo:hover {color: lime } /* éléments de formulaire, ou liens, sélectionnés */ :focus /* langue inférée pour le texte contenu dans l'élément */ p:lang(en)
Pseudo-classes qui reposent sur la position de l'élément dans l'arbre html.
/* premier et dernier fils d'un élément */ li:first-child, li:last-child { color: yellow; } /* selon la position parmi les enfants */ tr:nth-child(3) /* ligne troisième enfant du tableau */ tr:nth-child(3n) /* les lignes multiples de 3 */ tr:nth-child(even) /* lignes de numéro d'enfant pair */ tr:nth-child(odd) /* lignes de numéro d'enfant impair */
À noter également l'existence des pseudo-classes :first-of-type, :last-of-type et :nth-of-type(n). Dans ces cas, le comptage se fait sur les éléments de même nom.
Pseudo-classe qui opère la négation logique sur les sélecteurs.
/* éléments pas nav */ :not(nav) /* paragraphes mais pas de classe introduction */ p:not(.intro) /* des éléments qui ne soient pas premier fils */ :not(:first-child)
Notons qu'il est possible d'embarquer une conjonction dans la négation (en séparant par des virgules dans le not).
Terminons par la pseudo-classe :root qui désigne la racine du document, c'est-à-dire l'élément html pour les documents Web. Il y a peu d'intérêt à styler à ce niveau, mais il est d'usage de définir les variables globales de CSS à ce niveau. Ces variables permettent par exemple de paramétrer des couleurs ou des tailles pour l'ensemble de la feuille CSS.
/* définition de couleurs en variables globales */ :root { --couleur-principale: #fff; --couleur-secondaire: #000; } /* utilisation des variables globales */ body { color: var(--couleur-secondaire); background-color: var(--couleur-principale); } h1 { color: var(--couleur-principale); background-color: var(--couleur-secondaire); }
Par pseudo-éléments
Les pseudo-éléments permettent de repérer et de styler certains contenus qui ne sont pas balisés, comme si nous leur rajoutions des balises autour.
/* première ligne de chaque paragraphe */ P::first-line { ... } /* première lettre de chaque paragraphe */ P::first-letter { ... }
Pour les pseudo-éléments qui n'ont pas de contenu a priori, il est possible de leur en ajouter un avec la propriété content.
/* avant le contenu du paragraphe, juste après la balise ouvrante */ p::before { content("👉 "); } /* après le contenu du paragraphe, juste avant la balise fermante */ p::after { content(" 👈"); }
Stylage d'éléments html en CSS
Nous regardons maintenant ce qu'il est possible de spécifier dans l'apparence des éléments sélectionnés.
Propriétés de police
Plusieurs propriétés CSS permettent de préciser la police à utiliser pour afficher du texte :
- font-family : police à utiliser
- font-size : taille de la police avec précision de l'unité
- font-style : normal, italic, oblique
- font-variant : normal, small-caps
- font-weight : de 100 à 900, normal, bold
Par exemple pour des titres, on pourra écrire :
h3 { font-family: Wide Latin, Arial Black, Helvetica, sans-serif; font-size: 120%; font-variant: small-caps; font-weight: bold; }
Dans le cas de la propriété font-family, on indique la première police à privilégier mais, en cas d'absence, on précise une liste de choix de substitution (si l'une manque sur la machine du client, le navigateur essayera la suivante, etc.). Au pire, si aucune police n'a été trouvée, une police sera choisie dans la famille spécifiée en dernière position dans la liste. Cinq familles de polices sont reconnues :
- serif (Times, etc.)
- sans-serif (Arial, Helvetica, etc.)
- cursive
- monospace (machine à écrire comme Courrier)
- fantasy
Mise en forme du texte
- white-space : normal, pre, nowrap
- word-spacing : l'espace entre les mots
- letter-spacing : l'espace entre deux lettres au sein d'un même mot
- text-decoration : none, underline, overline, line-through, blink
- text-transform : none, uppercase, lowercase, capitalize
- text-shadow : caractéristiques de l'ombre projetée du texte
Exemple :
/* faire disparaître le soulignement des liens */
A:hover {
text-decoration: none;
}
Agencement du texte
- text-align : left, center, right, justify
- text-indent : taille du retrait de première ligne (alinéa)
- line-height : hauteur de ligne
- vertical-align : baseline, super, top, text-top, middle, text-bottom, bottom, sub
Mise en forme de listes
- list-style-type : type de numérotation ou de puce (none, decimal, lower-alpha, upper-alpha, lower-roman, upper-roman, disc, circle, square)
- list-style-image : permet de spécifier une image servant de puce pour les listes (none ou url suivie d'un nom de fichier image)
- list-style-position : position de la liste par rapport au reste du texte, décalage vers la droite ou pas (inside, outside)
- list-style : syntaxe courte pour les trois éléments précédents type/image/position
Couleurs et arrière-plans
Nous avons des propriétés pour définir la couleur du texte dans un élément, ainsi que sa couleur de fond. Mais nous avons également des propriétés pour gérer une image en fond de n'importe quel élément.
- color : couleur du texte
- background-color : couleur du fond
- background-image : image de fond (nom ou url)
- background-repeat : mode de réplication de l'image de fond (repeat, repeat-x, repeat-y, no-repeat)
- background-attachement : défilement ou non de l'image de fond (scroll, fixed)
- background-position : position d'ancrage du coin supérieur gauche de l'image de fond dans l'élément stylé
Exemple avec uniquement couleur du texte et couleur de fond.
body { color: #000000; background-color: #ccccff; }
Il est possible d'utiliser une image trop grande pour le fond de l'élément. On choisit alors la partie à afficher à l'aide de la propriété background-position et éventuellement des valeurs négatives pour cette propriété. Cela permet, entre autre, de mettre en œuvre la technique des sprites.
div#avion { /* définition d'un petit cadre */ height: 20px; width: 20px; /* placement d'une image de fond en dehors du cadre background: url('google-sprites.png'); background-position: -101px -191px; }
Retraits extérieurs et intérieurs
- margin, margin-top, margin-bottom, margin-left, margin-right : retraits extérieurs
- padding, padding-top, padding-bottom, padding-left, padding-right : retraits intérieurs
Encadrements
On peut viser l'encadrement complet (border) de l'élément ou seulement certains de ses quatre bords (border-top, border-bottom, border-left et border-right). Pour chacune de ces propriétés, on pourra préciser le style, l'épaisseur et la couleur du trait :
- border-style : solid, double, groove, ridge, inset, outset, dotted, dashed
- border-width : épaisseur du trait
- border-color : couleur du trait
- border : version compacte pour les trois informations
Code CSS pour encadrer un titre en rouge :
h1 { padding: 1em; border: solid 1px red; }
Numérotation automatique
Nous avons vu que le langage CSS permet des variables globales (ce sont d'ailleurs plutôt des valeurs qui restent disponibles mais constantes). Il existe un autre type de variables globales : des compteurs que l'on peut initialiser, incrémenter et afficher. Cela permet de numéroter automatiquement ce que l'on souhaite.
article { /* création et mise à zéro du compteur */ counter-reset: numsection; } article h2 { /* à chaque nouvelle section, on incrémente le compteur */ counter-increment: numsection; } article h2::before { /* devant chaque titre de section, on affiche le compteur */ content: counter(numsection) ". "; }
Nature/apparence de la boîte
On a déjà évoqué la propriété display avec ses valeurs possibles block et inline. Il y a aussi la valeur none qui fait disparaître l'élément concerné, et d'autres encore que nous discuterons dans la section suivante sur les mises en page en html/css.
Faire disparaître un élément est également possible avec la propriété visibility et la valeur hidden. Dans ce cas, le contenu de l'élément disparaît sans pour autant libérer la place occupée.
Des propriétés CSS permettent de définir largeur et hauteur d'un élément (width et height). Il est également possible de donner des valeurs minimales ou maximales pour largeur et hauteur (min-width et max-width, min-height et max-height). C'est alors le navigateur qui calcule largeur et hauteur, en tenant compte des contraintes spécifiées.
Si le contenu excède les largeur et hauteur, qu'elles soient spécifiées dans la CSS ou calculées par le navigateur, le débordement va être géré par la propriété overflow :
- visible : on laisse dépasser,
- hidden et clip : ce qui dépasse est invisible,
- scroll et auto : on propose des barres de défilement.
Ces propriétés CSS (display, largeur et hauteur, gestion des dépassements), seront utiles dans la mise en œuvre de plusieurs mises en page sophistiquées de documents Web.
Mises en page avec CSS
Nous allons chercher à déplacer les boîtes définies par le document html pour obtenir la meilleure mise en page possible pour l'utilisateur. Cependant, il faut avoir en tête que ce travail doit être fait en CSS, sans modifier le code html. Cela pour des raisons d'accessibilité et de bon référencement : l'ordre des éléments dans le code html doit rester optimal pour une personne malvoyante et pour un robot indexeur (dans ces deux cas, les visiteurs n'accèdent qu'au document html et pas à la CSS qui travaille la mise en page à l'écran).
Nous balayons différentes mises en page possibles en CSS :
- en table,
- en colonnes,
- avec des flottants,
- en jouant sur la propriété position,
- avec le module flexbox
- avec le module grid.
Tableaux
A priori, les tableaux html sont maintenant réservés à la structuration de données, et plus à la mise en page de documents. Cependant, on peut en CSS faire porter les notions de tableaux, lignes et cellules à n'importe quel élément. Cela se fait avec la propriété display qui peut prendre, en plus de celles déjà vues, les valeurs table, table-cell et table-row.
De la même manière, nous retrouvons le comportement des items des listes, avec la valeur list-item de la propriété display.
Dans le cas de documents html, l'intérêt reste limité mais cette technique est bien pertinente pour l'affichage de données en tableaux dans le cas de documents XML. Considérons par exemple le document suivant :
<film> <titre>Rocky</titre> <année>1976</année> <réalisateur>John G. Avildsen</réalisateur> <casting> <rôle><perso>Rocky Balboa</perso><acteur>Sylvester Stallone</acteur></rôle> <rôle><perso>Adrian</perso><acteur>Talia Shire</acteur></rôle> <rôle><perso>Paulie</perso><acteur>Burt Young</acteur></rôle> <rôle><perso>Apollo Creed</perso><acteur>Carl Weathers</acteur></rôle> <rôle><perso>Mickey</perso><acteur>Burgess Meredith</acteur></rôle> </casting> </film>
En CSS, on pourra commencer le stylage en positionnant les display de chaque élément présent dans le document XML :
/* film et titre en blocs */ film { display: block; } titre { display: block; } /* année et réalisateur en item de liste */ année, réalisateur { display: list-item; } /* le casting en tableau */ casting { display: table; } rôle { display: table-row; } perso, acteur { display: table-cell; }
Multi-colonnage
CSS permet d'afficher le contenu d'un élement en colonnes : on précise le nombre de colonnes ou leurs largeurs, l'espacement entre les colonnes et l'aspect du séparateur de colonnes. C'est alors le navigateur qui gère la distribution du texte dans les colonnes.
article { /* nombre et taille des colonnes */ column-count: 2; /* column-width: 400px; */ /* columns: 2 auto; */ /* syntaxe courte pour count/width */ /* intervalle entre les colonnes */ column-gap: 5em; /* aspects du trait entre les colonnes */ /* column-rule-style: solid; column-rule-width: 3px; column-rule-color: #add8e6; */ /* syntaxe courte pour style/width/color */ column-rule: solid 3px #add8e6; }
Ici il y a seulement des gouttières verticales entre les colonnes, si bien que seule la propriété column-gap a du sens. Retenons tout de même qu'il existe aussi row-gap pour espacer entre les lignes et gap qui est un raccourci pour exprimer en une ligne row-gap et column-gap.
Positionnement avec float
La propriété float (par défaut none) peut prendre les valeurs left et right. Si l'on fait flotter un élément, par exemple à gauche, le reste du contenu va venir se placer à sa droite. Pour cela il faut bien sûr que l'élément flottant ne prenne pas toute la largeur disponible (sa largeur pourra donc avoir été fixée avec la propriété width).
nav { width: 20%; float: left; }
Si la hauteur de l'élément flottant est trop importante et que des éléments continuent à venir se placer à côté alors que nous ne le souhaitons plus, il convient d'arrêter la flottement avec la propriété clear.
/* on associe clear à une séparation hr invisible */ hr { clear: both; /* on arrête le flottement à gauche et à droite */ visibility: hidden; }
Avec la propriété position
Par défaut, la propriété position des éléments vaut static. Les autres valeurs possibles sont :
- fixed : l'élément reste immobile au même endroit, même quand l'utilisateur fait défiler le document ;
- sticky : l'élément bouge avec le défilement du document mais s'immobilise quand l'emplacement qui lui a été assigné est atteint ;
- relative : on se décale par rapport à la position normalement calculée en static ;
- absolute : on se positionne par rapport aux bords de l'élément parent (les bords du navigateur s'il s'agit de BODY).
Pour ces valeurs listées de la propriété position, l'emplacement final de l'élément va être donné par les propriétés top, left, bottom et right (qui ont donc des sémantiques différentes selon la valeur de position).
nav { position: absolute; top: 2em; right: 2em; }
Cette technique de mise en page autorise la surperposition de plusieurs éléments. Dans ce cas (des éléments positionnés de manière non statique l'un sur l'autre), la propriété z-index indique dans quel ordre doivent apparaître les éléments superposés. La propriété opacity permet elle des effets de transparence sur les éléments empilés.
Module flexbox
Comme son nom l'indique, ce module permet d'obtenir des flexible box, autrement dit, des boîtes flexibles ou souples. Les propriétés propres à ce module sont préfixées par flex-.
Dans la logique des flexbox, on distingue un élément-parent destiné à être le conteneur ou le contenant, et ses éléments-enfants que l'on appellera les items. Des propriétés CSS peuvent être définies à ces deux niveaux.
CSS au niveau du conteneur flexible
On indique au niveau du conteneur un display flexible (flex ou inline-flex) et on choisit la direction dans laquelle vont se placer les éléments-enfants. Ce choix se fait avec la propriété flex-direction qui peut prendre les valeurs row, row-reverse et column, column-reverse. On parlera dans le cadre des flexbox d'axe principal pour cette direction choisie et d'axe secondaire pour l'autre direction. Les propriétés suivantes doivent s'interpréter selon la direction choisie.
La propriété justify-content permet la justification des enfants dans la direction choisie : soit au début de l'axe (start ou flex-start), soit à la fin (end ou flex-end), soit au milieu (center), soit espacés de différentes manières pour occuper toute la place disponible sur l'axe (space-between, space-around et space-evenly). flex-start et flex-end sont dépendants du reverse, présent ou non dans la direction choisie.
L'alignement des éléments enfants se fait avec la propriété align-items, ce qui revient à définir comment ils se placent l'axe secondaire : stretch, flex-start, flex-end, center ou baseline.
Pour chacune des deux dernières propriétés, quand l'axe vertical est concerné, il faut fixer la hauteur du père pour avoir un effet visible (quand il s'agit de l'axe horizontal, il y a une largeur naturellement définie, par exemple la largeur de la fenêtre du navigateur).
main { height: 100vh; display: flex; /* main est conteneur flexible */ flex-direction: column; /* items dans une colonne */ /* espacement équitable pour occuper toute la hauteur */ justify-content: space-evenly; /* centrage horizontal des enfants, dans la colonne */ align-items: center; }
Si l'on manque de place pour tout empiler dans la direction choisie, les boîtes se réduisent exagérément ou le contenu sort des boîtes. Dans cette situation, on peut autoriser le wrap avec la propriété flex-wrap (nowrap, wrap ou wrap-reverse) et les boîtes surnuméraires seront alors accueillies :
- sur une nouvelle ligne si l'on est en mode row,
- sur une nouvelle colonne si l'on est en mode column.
Si le wrap a été autorisé, la propriété align-content gère la justification sur l'axe secondaire : stretch, flex-start, flex-end, center, space-between ou space-around.
main { height: 100vh; display: flex; flex-direction: column; /* on autorise l'environnement flex sur plusieurs colonnes */ flex-wrap: wrap; /* justification des colonnes dans la largeur */ align-content: space-around; }
Peuvent également être utiles au niveau du conteneur :
- row-gap, column-gap, gap : espacements entre les cellules,
- flex-flow : un raccourci pour flex-direction et flex-wrap.
CSS au niveau des items flexibles
Avec la simple déclaration de l'élément parent en display flex, les éléments enfants deviennent automatiquement flexibles. De plus, nous avons également réglé les alignements et justifications au niveau du parent. Il reste donc en général rien ou peu de choses à faire au niveau des enfants-items.
Les propriétés suivantes sont cependant disponibles.
- flex-basis : donne une idée de la taille shouhaitée (largeur ou hauteur selon la flex-direction choisie). Cela correspond aux habituelles propriétés width et height mais en plus souple : flex-basis n'est pas une contrainte stricte. Il est possible d'indiquer auto (valeur par défaut) ou une valeur numérique. Nota bene : il est parfois utile d'indiquer 0 plutôt que auto.
- order : modifie l'ordre de l'élément-enfant.
- align-self : écrase localement le choix général fait au niveau du parent-conteneur avec la propriété align-items (mêmes valeurs possibles).
- flex-grow : indique la proportion de la place disponible à occuper. La place occupée dépend finalement des valeurs des autres enfants pour cette propriété. Prend pour valeur un nombre (0, 1, 2, etc.).
- flex-shrink : autorise ou non le rétrecissement (valeurs possibles : 1 ou 0).
- flex : rassemble les valeurs flex-grow, flex-shrink et flex-basis. Attention les valeurs « initiales » ne sont pas les valeurs par défaut si l'on omet de les préciser.
p { order: -1; /* on envoie l'élément devant les autres enfants */ align-self: stretch; /* l'élément va s'étendre au maximum sur l'axe secondaire */ flex-grow: 1; /* l'élément est autorisé à s'étendre sur l'axe principal... */ flex-basis: 0; /* ... à partir d'une taille minimale */ }
Les propriétés non préfixées par flex- (gap, order, justify-content, align-items, align-content, etc.) sont disponibles dans d'autres contextes. Nous les retrouverons par exemple dans le contexte des mises en page à base de grilles.
Module grid
Le principe est de définir des lignes et des colonnes et de raisonner sur le quadrillage ainsi tracé. Les éléments html viendront se placer dans les cellules de la grille et nous verrons qu'un élément pourra même s'étendre sur plusieurs cases contigües.
Le grid layout définissant d'emblée une grille avec lignes et colonnes, il n'y a pas ici d'axe privilégié. Nous évitons ainsi la dissymétrie (parfois désagréable ?) des flexbox, dont on a vu qu'elles privilégiaient une direction, horizontale ou verticale. Le propriétés communes aux flexbox et aux grid ont donc parfois des sémantiques un peu différentes (mais peut-être plus facile à appréhender dans le cas des grid ?). Les propriétés propres au module étudié dans cette section sont préfixées par grid-. Nous retrouvons à nouveau les notions de conteneur et d'enfants-items. Le code CSS associé au conteneur joue deux rôles majeurs : définir la grille et préparer la mise en page des éléments-enfants dans la grille.
CSS niveau conteneur : découpage de la grille
Cette fois, tout commence avec la propriété display qui prend la valeur grid (ou inline-grid). On définit les lignes et les colonnes de la grille avec les propriétés grid-template-rows (suivie des hauteurs données à chaque ligne) et grid-template-columns (suivie des largeurs données à chaque colonne). Notons que grid-template permet d'exprimer en même temps grid-template-rows et grid-template-columns (séparés par un slash), même si cette écriture semble moins lisible. On va préciser largeurs et hauteurs en utilisant les unités déjà vues, en particulier l'unité pourcentage qui est pratique et dont on sait qu'elle va produire une mise en page fluide. La valeur auto est également possible : c'est alors le navigateur qui va calculer une valeur.
/* définition d'une grille 3 lignes × 2 colonnes */
main {
width: 100%;
height: 100vh;
display: grid;
grid-template-rows: 20% auto 20%;
grid-template-columns: 50% 50%;
}
Poursuivre avec l'unité pourcentage va être délicat quand nous introduirons des espacements (marges, gouttières, etc.). Une solution déjà aperçue est d'utiliser calc() pour calculer le bon pourcentage en intégrant dans le calcul les largeurs des espacements. Mais les calculs seront encore compliqués par des colonnes ou lignes en auto. Plus simple, il est possible d'utiliser dans ce contexte l'unité fr : une unité fr vaut une fraction de l'espace disponible après positionnement des marges et des colonnes/lignes en auto. Cette unité peut être utilisée pour les lignes comme pour les colonnes.
En résumé, la valeur auto, l'unité pourcentage et l'unité fr sont utilisables pour les colonnes et pour les lignes. Cependant, pour leur bon fonctionnement en vertical, il faut avoir fixer la hauteur du parent-grille.
/* définition d'une grille avec calculs laissés au navigateur */
main {
width: 100%;
height: 100vh;
display: grid;
grid-template-rows: 1fr auto 1fr;
grid-template-columns: 1fr 1fr;
}
Ainsi, notre mise en page à base de grille sera fluide et répondra convenablement au redimensionnement du navigateur. Deux fonctions CSS existent pour faciliter encore la définition des dimensions des lignes et des colonnes de la grille :
- repeat() : permet de repéter plusieurs fois une même définition,
- minmax() : définit les valeurs minimales et maximales.
Enfin, il est possible de laisser le navigateur décider du nombre de lignes ou de colonnes.
- auto-fill : ajoute colonne ou ligne tant qu'il reste de la place, même si cela induit plus de cellules que d'éléments à placer,
- auto-fit : idem mais en se limitant au nombre de d'éléments à placer.
/* on tente un nombre de colonnes correspondant au nombre d'éléments-enfants
si l'on peut avoir des colonnes d'au moins 20em de largeur
(sinon tant pis, on aura moins de colonnes que d'éléments
toutes les colonnes auront la même largeur */
grid-template-columns: repeat(auto-fit,minmax(20em,1fr));
CSS niveau conteneur : préparation des placements
On peut envisager de ne rien faire : le placement sera implicite, les enfants iront dans les cellules, dans l'ordre. Pour un placement plus sophistiqué, il faut l'expliciter et, pour cela, il est nécessaire de se repérer dans la grille. Plusieurs moyens sont possibles.
Soit avec les numéros implicites des traits entre les lignes et les colonnes. Par exemple, quand on crée deux colonnes, il y a trois traits verticaux numérotés 1, 2 et 3 (ou, dans le même ordre -3, -2 et -1).
Soit on nomme nous-mêmes les traits lors de la définition des lignes et des colonnes, avec les noms donnés entre crochets.
/* noms basés sur les numéros */ grid-template-columns: [trait-1] 1fr [trait-2] 1fr [trait-3]; /* noms avec les positions nommées */ grid-template-columns: [trait-gauche] 1fr [trait-milieu] 1fr [trait-droit]; /* noms en référence aux colonnes */ grid-template-columns: [col-1-déb] 1fr [col-déb-2] 1fr [col-fin];
Soit on nomme les zones avec la propriété grid-template-areas. On mime la structure de la grille et on nomme chaque cellule. Si deux cellules portent le même nom, cela signifie qu'elles seront fusionnées et qu'elles accueilleront le même élément.
/* mise en page avec une ligne d'entête,
une colonne de navigation à côté de la colonne de contenu,
et une ligne de pied de page */
main {
display: grid;
grid-template-rows: 1fr auto 1fr;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"entête entête"
"navigation contenu"
"pied pied"
}
À noter que le nommage des zones induit de nouveaux noms pour les traits de la grille. Par exemple, avec la dernière ligne de l'exemple ci-dessus, le trait 1 pourra être désigné par pied-start et le trait 3 par pied-end.
CSS niveau conteneur : gestion des gouttières
Les propriétés row-gap, column-gap et gap permettent de définir les espaces sur les traits de la grille (gouttières).
main { display: grid; gap: 2em; }
CSS niveau conteneur : alignement/justification des enfants
On définit au niveau du parent-conteneur les stratégies globales d'alignement et de justification des enfants dans la grille. Commençons par les justifications des lignes dans la hauteur et des colonnes dans la largeur.
/* justification horizontale des colonnes */ justify-content: start | end |center | space-between | space-around | space-evenly; /* justification verticale des lignes */ align-content: stretch | start | end | center | space-between | space-around; /* version compacte align-content + justify-content */ place-content: center end;
Et les placement des enfants-items dans leurs cellules :
/* justification horizontale des éléments dans les cellules */ justify-items: stretch | start | end | center | baseline; /* justification verticale des éléments dans les cellules */ align-items: stretch | start | end | center | baseline; /* version compacte justify-items + align-items */ place-items: center center;
CSS niveau conteneur : cellules supplémentaires
Il peut arriver que le nombre d'enfants-items excède le nombre de cellules prévues initialement dans la grille. Dans ce cas, le navigateur va ajouter automatiquement des cellules. Dans notre code CSS, au niveau conteneur, on peut préciser comment doivent se passer ces ajouts.
- grid-auto-flow : stratégie d'ajout des nouvelles cellules (row, column, sparse ou dense),
- grid-auto-columns et grid-auto-rows : largeur des colonnes et des lignes supplémentaires.
CSS niveau des items de la grille
À nouveau, beaucoup a été fait au niveau du conteneur de grille. Il reste peu de choses à préciser au niveau des enfants-items, en particulier si l'on laisse faire le placement implicite : les enfants se répartissent dans les cases, dans l'ordre d'apparition dans le code html.
Si l'on veut modifier cet ordre suggéré par le code html, une première solution est de recourir à la propriété order, déjà vue dans la section sur flexbox.
Si l'on veut placer explicitement les éléments, CSS grid nous permet d'indiquer pour chaque enfant son emplacement dans la grille. Une possibilité est d'utiliser les numéros (positifs ou négatifs) ou les noms des traits au sein des propriétés suivantes :
- grid-column-start, grid-column-end et grid-column : permettent de préciser le trait de début de colonne et le trait de fin de colonne ;
- grid-row-start, grid-row-end et grid-row : permettent de préciser le trait de début de ligne et le trait de fin de ligne.
/* placement du pied à l'aide des numéros de traits */ footer { grid-row: 3 4; grid-column: 1 3; } /* placement du pied à l'aide des numéros négatifs de traits */ footer { grid-row: -1 -2; grid-column: -1 -3; } /* placement du pied à l'aide des noms des traits */ footer { grid-row: 3 4; grid-column: trait-gauche trait-droit; }
Le mot-clef span pour étaler à partir d'une position.
/* élément qui s'étend sur deux colonnes */
footer {
grid-row: 3 4;
grid-column: 1 / span 2;
}
Finalement, nous préférerons le placement explicite avec les noms de régions donnés dans l'élément-conteneur. C'est la propriété grid-area qui permet d'associer l'élément à un nom de zone définie dans le parent-conteneur.
/* placement du pied à l'aide d'une zone définie et nommée */
footer {
grid-area: pied;
}
Quelques variations sont encore possibles avec les propriétés suivantes :
- z-index et opacity : gèrent la superposition de plusieurs éléments dans une même zone de la grille, puisque cela est autorisé par les grid,
- justify-self / align-self / place-self : changent les justifications pour un item particulier.
Diffusion vers différents médias
Nous avons vu que nous pouvions adapter certaines mises en forme aux préférences de l'utilisateur, en utilisant systématiquement des unités de mesure relatives. C'est une bonne pratique mais elle est insuffisante pour prétendre s'adapter à tous les appareils. Dans cette section, nous verrons comment cibler certains appareils spécifiques, et même comment prendre en compte les caractéristiques de chaque appareil.
Association de styles à un média particulier
Il est possible de préciser le média auquel est dédié une feuille de style. Les médias possibles sont : all, aural, braille, embossed, handheld, print, projection, screen, tty et tv.
La précision du média visé peut se faire dans l'en-tête de la page HTML :
<LINK REL="stylesheet" HREF="site.css" MEDIA="screen"> <LINK REL="stylesheet" HREF="print.css" MEDIA="print">
ou au sein d'une même feuille de style :
@media print { BODY { font-size: 10pt } } @media screen { BODY { font-size: 12pt } }
Aujourd'hui les objets connectés permettant de visualiser des pages Web sont devenus très divers et ce mécanisme n'est plus suffisant pour traiter toutes les possibilités. En particulier, handheld ne peut pas permettre de spécifier un affichage qui conviendrait à tous les smartphones, tablettes et ordinateurs portables !
Media queries et responsive Web design
La métadonnée viewport permet des réglages sur le navigateur du client. Avec cette ligne dans l'entête html, on prend la largeur disponible et on neutralise le zoom en le fixant à 1.
<meta name="viewport" content="width=device-width, initial-scale=1">
Cela définit les unités viewport, ainsi que les dimensions de la fenêtre, sur lequelles on peut ensuite travailler.
Les media queries vont nous permettre d'obtenir des informations sur l'appareil utilisé par le visiteur de notre site Web. Ici la taille de l'écran :
/* nos styles pour les écrans les plus petits */ @media screen and (max-width: 640px) { body { color : red; background-color: black; } } /* nos styles pour les écrans plud grands */ @media screen and (min-width: 640px) and (max-width: 1280px) { body { color : black; background-color: red; } }
Sur ce sujet, il y a un principe à retenir : définir une CSS simple pour les petits écrans puis la complexifier dans une media query dédiée aux écrans plus grands (principe mobile first). Le principe inverse, définir une CSS complexe pour les grands écrans puis la dégrader dans une media query pour les petits écrans est nommé responsive degradation. Cette dernière stratégie est préjudiciable aux petits appareils mobiles qui, bien que plus faibles, doivent dans ce cas calculer une mise en page complexe... avant d'y renoncer. C'est pourquoi on privilégiera systématiquement la stratégie mobile first.
Il est également possible de connaître l'orientation de l'appareil.
/* si l'écran est en mode portrait */
@media screen and (orientation:portrait) {
main {
width: 100%;
margin: 0;
padding: 0;
}
}
Enfin, on peut être renseigné sur la préférence mode sombre ou mode clair choisie sur l'appareil.
/* nos styles en mode clair */ @media (prefers-color-scheme: light) { body { color: black; background-color: white; } } /* nos styles en mode sombre */ @media (prefers-color-scheme: dark) { body { color: white; background-color: black; } }
Le média print
On a vu que l'attribut media pouvait prendre la valeur print. Nous regardons les bonnes pratiques CSS propres à ce média. En particulier, il s'agit de faciliter la lecture du contenu imprimé et d'économiser les consommables (papier, encres couleurs, etc.). En particulier, on pourra penser aux points suivants :
- supprimer les éléments inutiles sur papier,
- écrire noir sur blanc,
- bien choisir la police,
- bien choisir la taille de la police,
- justifier le texte,
- prendre garde aux contenus qui débordent de la page,
- gérer au mieux les sauts de page,
- définir le format du papier,
- réduire les marges,
- styler pertinemment les liens.
Pour les six premiers points :
/* suppression des boîtes de navigation */ nav { display: none; } /* choix de l'écriture : couleur, police, taille */ body { color: black; font-family: serif; font-size: 12pt; } /* justification des paragraphes */ p { text-align: justify; } /* débordements */ table, figure, pre { overflow: visible; }
Concernant les sauts de page (page-break), on peut les contrôler avant (before), dans (inside) et après (after) chaque élément, avec les valeurs possibles auto, always et avoid.
article { page-break-before: always; } section { page-break-inside: avoid; } h2, h3 { page-break-before: avoid; }
Avec la gestion des sauts de page, viennent les notions de veuves et d'orphelines.
- Lignes veuves
- Lignes isolées en haut de page, l'essentiel du texte étant sur la page précédente.
- Lignes orphelines
- Lignes isolées en bas de page, l'essentiel du texte étant sur la page suivante.
Les propriétés CSS widows et orphans fixent les valeurs minimales acceptables pour le nombre de ces lignes. Par défaut c'est souvent deux :
- au moins deux lignes en fin de page, jamais une seule en bas de page,
- au moins deux lignes en début de page, jamais une seule en haut de page.
p { widows: 5; orphans: 5; }
Dans une règle @page, il est possible de préciser des paramètres propres à une page imprimée comme le format du papier et les marges.
@page { size: A4 portrait; margin: 18mm 20mm 8mm 20mm; }
Les liens ne sont bien sûr plus cliquables sur la version papier. On peut vouloir enlever toute mise en forme des liens pour faciliter la lecture mais afficher tout de même l'URL du lien pour transmettre l'information au lecteur.
a { color: black; text-decoration: none; } a::after { content: attr(href); }
Conclusion
Nous avons vu les principes de la mise en forme de documents Web avec le langage CSS, mise en forme séparée de la structuration html qui elle peut ainsi rester purement sémantique.
Nous avons vu les capacités de CSS à travers ses sélecteurs de parties du document et ses propriétés de mise en forme.
Ces propriétés permettent plusieurs modèles de mise en page, en particulier les plus modernes, simples et sophistiqués à la fois : les flexible box et le grid layout.
Enfin, nous avons discuté de l'adaptation de ces mises en page pour différents médias et dans le but d'avoir un responsive Web design : technique des media queries, principe du mobile first pour les écrans et idées principales pour l'édition papier de documents Web.