CSS est difficile, à ce qu’il paraît. Mais avez-vous déjà essayé d’écrire du CSS en vous battant contre un framework qui modifie votre DOM ?

Le problème

Je travaille sur une interface simple : header, contenu, footer. Le header et le footer ont une taille fixe, et je souhaite positionner le contenu au centre de l’espace restant.

Ah, flexbox, mon vieil ami, ainsi nous nous retrouvons !

Moi-même (pas vraiment, c’est juste pour l’ambiance)

Me voilà donc avec cette implémentation simple :

<body>
  <header></header>
  <main></main>
  <footer></footer>
</body>
body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
Le main correctement placé au centre de l’espace vide.
Chacun des trois descendants directs du body a une bordure bleue. Jusqu’ici, tout va bien.

Je suis donc heureux, comme tout développeur front quand les choses s’alignent correctement.

Mais alors que l’application grandit, je réalise que je vais avoir besoin d’englober le main et le footer à l’intérieur d’un composant Angular unique, pour des raisons structurelles propres à l’application.

C’est alors que ma belle page se brise, et vous pouvez comprendre pourquoi en regardant la structure générée par l’apparition de ce nouveau composant :

<body>
  <header></header>
  <my-component>
    <main></main>
    <footer></footer>
  </my-component>
</body>

Hélas ! Flexbox ne s’intéresse à rien d’autre qu’à ses propres enfants directs, qui sont maintenant header et my-component.

Le header est placé en haut, le main et le footer sont ensembles, collés en bas.
Regardez les bordures bleues : le body n’a plus que deux enfants directs.

Ma première idée fut de faire de my-component un conteneur flex lui-même, mais je n’allai pas très loin avec cette idée.

my-component {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
Le main est collé sous le header, avec un grand espace entre le main et le footer.
my-component est un conteneur flex lui-même... Mais cela ne centre pas le bloc main pour autant.

Pensez-y un instant : comment vous y prendriez-vous ?

Une méthode douteuse

Je sais que vous n’y avez pas pensé un instant et que vous avez continué votre lecture. Je ne vous en veux pas.

Une méthode à laquelle je n’avais pas pensé avant d’écrire cet article est d’ajouter un pseudo-élément à my-component :

my-component::before {
  content: "";
}

my-component a désormais trois enfants : le pseudo-élément before, le main et le footer.

Voici le résultat en action !

Voir le code sur CodePen.

Il n’y a qu’à remplacer le contenu du pseudo-élément par une chaîne vide, et voilà ! Parfois, une solution un peu sournoise comme celle-ci est si simple qu’elle en devient attractive...

Une méthode plus propre

Mais ajouter un pseudo-élément juste pour tromper flexbox ne me paraît pas extrêmement propre !

Il y a quelques semaines, j’ai lu cet excellent article de Rachel Andrews : Digging Into The Display Property: Box Generation.

En lisant les informations à propos de display: content, j’avais deux pensées parallèles :

Mais en travaillant sur mon problème de layout, l’article me revint à l’esprit. En voici un extrait :

La valeur display: contents retire la boîte (box) à laquelle elle est appliquée de l’arbre des boîtes (box tree), de la même façon que display: none, mais en laissant les enfants en place.

Rachel Andrews, Digging Into The Display Property: Box Generation

En d’autres termes, cela sera équivalent (dans notre cas !) à ce qu’il se passerait si l’on commentait l’élément (mais pas ses enfants) :

<body>
  <header></header>
  <!-- <my-component> -->
  <main></main>
  <footer></footer>
  <!-- </my-component> -->
</body>

Et voici comment procéder dans les faits :

my-component {
  display: contents;
}
Note : En réalité, certaines propriétés CSS (font-size, color...) appliquées à l’élément impactent toujours ses enfants. Donc commenter l’élément n’est pas strictement équivalent, mais vous avez l’idée.

Donc... Cela signifie-t-il que du point de vue de flexbox, main et footer sont de nouveau des enfants directs du body ? Oui ! my-component n’a plus besoin d’être un conteneur flex lui-même. La propriété justify-content: space-between appliquée au body fonctionne de nouveau.

C’est la solution la plus courte présentée dans cet article : un sélecteur et une propriété. Presque magique.

Voir le code sur CodePen.

Notez que la bordure bleue qui entourait my-component a disparu : en effet, la boîte n’est plus rendue, donc les propriétés correspondantes (margin, padding, border...) n’ont plus d’effet.

Bien que j’aime la belle simplicité de cette solution, display: contents possède ses inconvénients.

display: contents peut provoquer des problèmes d’accessibilité, car l’élément est caché de l’arbre d’accessibilité. Si vous l’utilisez sur un élément ul, l’information qu’il s’agit d’une liste sera perdue dans de nombreux navigateurs.

Ce n’est pas un problème dans notre cas, cependant, puisque my-component ne véhicule pas de sens sémantique particulier. Mais l’on pourrait toutefois se méfier du support navigateur, qui est dans cette zone grise que j’appelerai la zone meh....

Mise à jour 2024 : display: contents est désormais largement supporté, bien que caniuse.com rapporte toujours de nombreux problèmes d’accessibilité. À juger au cas par cas, donc.

Une méthode encore plus propre

Voici donc la technique margin: auto.

Ce n’est qu’en rédigeant cet article que je me suis souvenu d’un autre article, un des meilleurs que j’ai lu sur le sujet: Flexbox’s Best Kept Secret, de Sam Provenza. Il date de 2015 et j’ai utilisé ce « secret » un grand nombre de fois depuis.

Quatre ans plus tard, je ne suis pas sûr qu’il soit bien connu. Mais vous pouvez utiliser margin-left: auto, pas exemple, pour « pousser » un élément flexbox aussi loin que possible vers la droite.

Voyez plutôt ce Codepen. L’élément de prix possède la propriété margin-left: auto.

Voir le code sur CodePen.

C’est extrèmement utile ! Avec cette technique, il devient possible de pousser un élément dans n’importe quelle direction, horizontale ou verticale.

Mais il y avait une partie de l’article que j’avais oubliée :

Si vous ne spécifiez pas de direction et que vous appliquez simplement margin: auto, l’élément distribura l’espace disponible des deux cotés, équitablement.

Sam Provenza, Flexbox’s Best Kept Secret

Ainsi, le « secret » fonctionne aussi avec plusieurs marges auto.

Et la voici donc, cette merveilleuse solution à notre problème : faire de my-component un conteneur flex de nouveau, et utiliser margin: auto 0 sur l’élément main.

Mise à jour 2024 : Utiliser la propriété logique margin-block: auto nous éviterait d’avoir à spécifier le 0, qui m’a toujours un peu gêné...
my-component {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

main {
  margin: auto 0;
}

Et voici le résultat en action.

Voir le code sur CodePen.

L’espace libre est distribué équitablement entre le dessus et le dessous de l’élément main.

Pas de pseudo-élément magique. Moins de risque d’accessibilité et de compatibilité. Simplement la majestueuse et toute puissante flexpower™.

L’épopée du centrage vertical ne s’achève jamais.

Cet article a été initialement publié sur dev.to

Aucun commentaire pour le moment

En réponse à
En validant, vous acceptez que Netlify stocke les données liées à votre commentaire.
Merci ! Votre commentaire a bien été enregistré et apparaîtra prochainement.