Aligner des éléments avec CSS Grid et display: contents
lundi 28 août 2023
La semaine derniÚre, je me suis retrouvé face à un dilemme en CSS : comment faire pour aligner les images des articles alors que la taille de mes textes sont dynamiques ?
Comme vous pouvez le voir, si on utilise une approche classique, ce nâest pas trĂšs esthĂ©tique : les images sont dĂ©calĂ©es, on a lâĆil qui accroche sans que ce soit rĂ©ellement lâintention.
Dans cet article, je vais vous prĂ©senter en quelques mots les diffĂ©rentes approches auxquelles jâai pensĂ© avant de vous parler de celle qui aujourdâhui me satisfait le mieux. Par ici, si vous voulez directement la solution.
Avant le style, le HTML
Avant de commencer Ă penser CSS, il faut dĂ©jĂ quâon ait en tĂȘte le HTML qui permet dâafficher ce contenu. Notamment le but est dâĂȘtre le plus sĂ©mantique possible. Par exemple ici avec lâutilisation de la balise article, en prenant bien soin de mettre le bon niveau de heading, etc.
<div class="article-list">
<article class="article">
<h2 class="article__title">Pawsitively Mysterious Purring</h2>
<!--
N'oubliez pas d'optimiser vos images en vous referrant Ă mon super article đ
https://www.julienpradet.fr/tutoriels/optimiser-le-chargement-des-images/
-->
<img
alt="A cute random kitten"
src="https://placekitten.com/g/300/200"
width="300"
height="200"
/>
<p>
Cats are the only animals that can purr while both inhaling and exhaling,
and the exact reason behind this unique behavior remains a scientific puzzle.
</p>
</article>
<article class="article">
<!-- Idem -->
</article>
</div>
Les approches abandonnées
PremiÚre option : tronquer les textes
Une solution, certainement la plus simple techniquement est de tronquer les textes. Ainsi, on sâassure que rien ne dĂ©passe jamais et que tout est parfaitement alignĂ©.
.article-list {
display: grid;
grid-template-columns: repeat(3, 18rem);
gap: 0 1rem;
width: fit-content;
margin: 0 auto;
}
.article__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
đĄ Petite astuce technique : pour que
text-overflow: ellipsisfonctionne, il faut que lâĂ©lĂ©ment ait une taille fixe. Cela peut ĂȘtre un%ou comme ce que jâai fait ici une taille fixe (18rem). Mais des unitĂ©s telles que1frouautone fonctionneraient pas parce que le navigateur adapterait la largeur des articles plutĂŽt que de bloquer la largeur du titre.
Le problĂšme de cette approche, câest quâon va, par essence, dĂ©couper du texte. Et cela peut amener Ă des situations⊠cocasses.
Plus dâexemples et explications dans le Toot de Natouille â#UXWriting du jour : tronquer les textesâŻ? Mauvaise idĂ©e.â sur Mastodon.
Dans mon cas, ça avait lâinconvĂ©nient supplĂ©mentaire que le titre Ă©tait utilisĂ© pour des noms dâartistes. Si jâĂ©cris âAnne-Sophie BâŠâ, ça ne sert pas Ă grand chose. Il y a certainement plein dâartistes dans le monde qui ont leur nom de famille qui commence par âBâ. Et au delĂ de lâaspect utile, je trouve que câest tout simplement une marque de respect que de ne pas Ă©corcher les noms.
DeuxiÚme option : réserver suffisamment de place
Au maximum, mes titres peuvent ĂȘtre sur 3 lignes. QuâĂ cela ne tienne, je vais simplement rĂ©server suffisamment de place pour afficher 3 lignes puis aligner sur cette hauteur le texte en bas.
.article__title {
/* J'aligne par le bas grĂące Ă flexbox */
display: flex;
align-items: end;
/* 3 lines * font-size * line-height */
min-height: calc(3 * 1em * 1.3);
}
A nouveau plusieurs inconvénients :
-
Aujourdâhui je sais que je ne dĂ©passerai jamais les 3 lignes. Que se passera-t-il si quelquâun·e ajoute un nouvel article qui a un titre sur 4 lignesâŻ? Que se passe-t-il pour les traductions dans les autres langues (exemple au hasard : lâallemand qui a tendance Ă ĂȘtre bien plus long que lâanglais)âŻ?
Pour cette raison je vous conseille fortement de toujours utiliser
min-heightplutÎt queheight. En effet, en passant parmin-height, vous vous assurez que votre contenu reste lisible. Mais à a ne vous prémunira pas du décalage. -
Que se passe-t-il si la sĂ©lection dâarticle du moment nâa que des titres courtsâŻ? On aura un trop grand espace blanc rĂ©servĂ© au dessus des articles. Ce nâest pas idĂ©al non plus.
TroisÚme option : aligner par le bas
Dans certains cas, aligner par le bas peut ĂȘtre une option :
.article-list {
display: grid;
grid-template-columns: repeat(3, 18rem);
/* La totalité d'un article est aligné par le bas grùce à align-items */
align-items: end;
gap: 0 1rem;
width: fit-content;
margin: 0 auto;
}
Mais dans notre cas, ce nâest pas possible parce que non seulement la taille des titres est dynamique, mais la taille des descriptions sous lâimage lâest aussi. Ainsi, mĂȘme si la derniĂšre ligne des descriptions est alignĂ©e, lâimage ne lâest pas.
Ce nâest pas une solution qui nous convient, mais bien des situations, cette option mâa sauvĂ© lorsque les Ă©lĂ©ments du bas Ă©taient de taille fixe.
CSS Gird & display:contents Ă la rescousse
Venons-en donc Ă la solution que jâai finalement adoptĂ©e.
Ma premiÚre idée était de considérer que, si je veux aligner convenablement les éléments, je vais devoir passer par une grille qui va sous découper mes articles en plusieurs parties.
En rĂ©flĂ©chissant de la sorte, ça me permet de forcer lâalignement des lignes de mon tableau. De plus, il est alors possible dâavoir des rĂšgles dâalignement diffĂ©rentes en fonction de la ligne traitĂ©e (titre = alignĂ© en bas, description = alignĂ© en haut).
Cependant, par dĂ©faut, CSS Grid ne permet de positionner que ses enfants directs : il serait donc nĂ©cessaire dâaplatir la structure de notre HTML en enlevant la balise article pour pouvoir les placer individuellement.
<div class="article-list">
<h2 class="article__title">...</h2>
<img ... />
<p>...</p>
<h2 class="article__title">...</h2>
<img ... />
<p>...</p>
<h2 class="article__title">...</h2>
<img ... />
<p>...</p>
</div>
Et le CSS devrait ressembler à quelque chose de ce style :
.article-list {
display: grid;
/* On indique au navigateur de remplir les columns en premier
* afin que le titre, l'image et la description soient sur une
* mĂȘme colonne plutĂŽt que sur une mĂȘme ligne */
grid-auto-flow: column;
/* On indique qu'il peut y avoir 3 lignes, pour qu'au 4Úme élément
* on passe sur une nouvelle colonne */
grid-template-rows: repeat(3, auto);
grid-auto-columns: 18rem;
gap: 0 1rem;
margin: 0 auto;
/* Les lignes suivantes ne sont pas en lien direct avec ce tuto mais
* permettent de centrer les articles horizontalement quand il y a assez
* de place et de les aligner Ă gauche quand il n'y en a plus assez.
* (Si vous ne voulez pas de scroll mais réduire le
* nombre de colonnes à la place, privilégiez des media queries) */
max-width: fit-content;
overflow: auto;
}
.article__title {
/* On s'assure que le titre est bien aligné en bas */
align-self: end;
}
â ïž Ca marche, mais on a vraiment pas envie dâenlever la balise article. Cela nuirai notamment au SEO en dĂ©tĂ©riorant la sĂ©mantique de votre page. Bof.
A la place, on va plutĂŽt expliquer au navigateur que certes il y a une balise article dans le HTML
mais que pour gĂ©rer son CSS, il devra faire comme si elle nâexiste pas (= lire le HTML comme lâexemple ci-dessus).
Et pour lui dire ça, on va utiliser la rÚgle display: contents.
.article {
display: contents;
}
Tout le reste du CSS que jâavais Ă©crit juste au dessus peut rester identique âš
On a donc tout le bĂ©nĂ©fice des CSS grids sans pour autant devoir nĂ©gocier avec le HTML. Et ça faisait longtemps que je nâavais pas eu besoin de triturer mon HTML pour des questions de style, alors jâavais envie de vous partager ça.
đĄ Cette rĂšgle
display: contentsest trĂšs peu connue (en tout cas jâai mis trĂšs longtemps avant de la dĂ©couvrir). Mais elle est peut-ĂȘtre utilisĂ©e Ă plus dâendroits que vous ne lâimaginez. Notamment vous ĂȘtes peut-ĂȘtre familier avec la notion de<Fragment>enReact. Et bienSveltea choisi de lâimplĂ©menter en insĂ©rant une balisediv style="display: contents"Ă la place de<svelte:fragment>dans le HTML gĂ©nĂ©rĂ©. Câest dâailleurs comme ça que jâai dĂ©couvert son existence.Par ailleurs, dâautres articles avant moi parlent de cette mĂ©thode dâutilisation (More accessible markup with
display: contents). Mais je nâen avais pas connaissance avant de faire des recherches pour cette article, alors le web mĂ©rite bien un nouvel article sur le sujet đ
A propos de lâaccessibilitĂ© de display: contents
Je vous ai dit plus haut que display: contents faisait disparaĂźtre la balise aux yeux du navigateur. Au niveau des specs, câest vrai uniquement pour le style. Par contre, la balise doit rester disponible notamment pour lâaccessibilitĂ© de la page.
Cela dit, les navigateurs ont longtemps eu des bugs Ă ce sujet. Câest loin dâĂȘtre mon expertise et Hidde en parle bien mieux que moi dans Accessibility concerns with current browser implementations.
Le TL;DR est que sur certains Ă©lĂ©ments, ça peut encore poser de rĂ©els problĂšmes aujourdâhui. Notamment, Ă©vitez Ă tout prix dâutiliser display: contents sur des Ă©lĂ©ments interactifs (ex: button) ou des Ă©lĂ©ments qui ont des roles importants pour la bonne comprĂ©hension de votre page (ex: ul, li, tr, td, etc.). Veillez donc Ă toujours bien tester avec des screen readers avant de vous lancer. Cette page tient Ă jour les soucis dâaccessibilitĂ© quâil peut exister : https://adrianroselli.com/2022/07/its-mid-2022-and-browsers-mostly-safari-still-break-accessibility-via-display-properties.html
Bonus : subgrid
Pour ces mĂȘmes cas dâusage, une nouvelle fonctionnalitĂ© CSS est en train dâarriver : subgrid.
En quelques mots, câest une valeur que vous pouvez utiliser dans la dĂ©finition de vos grid afin de prĂ©ciser Ă une grille enfant de sâaligner avec les lignes et colonnes de la grille parente. Dans les faits, cela sera plus puissant que de juste appeler display: contents et câest quelque chose qui mĂ©ritera sĂ»rement votre attention Ă lâavenir.
Cependant Ă ce jour câest supportĂ© par seulement 18% des navigateurs. La bonne nouvelle câest que câest en train dâarriver dans Chrome 117 et donc on peut espĂ©rer une adoption large dâici quelques mois quand ce sera essaimĂ© dans les diffĂ©rents navigateurs basĂ©s sur Chromium.
Bonus 2 : Avez-vous dĂ©jĂ rĂȘvĂ© dâun ::after-outerâŻ?
Les pseudo Ă©lĂ©ments :before et :after fonctionnent en ajoutant visuellement un balise span en dĂ©but ou en fin des enfants dâun Ă©lĂ©ment. Ainsi, le CSS suivant :
.element::before,
.element::after {
content: 'đ';
display: block;
}
Nous donne lâĂ©quivalent de ce DOM :
<div class="parent">
<div class="element">
::before
<!-- les enfants classiques -->
::after
</div>
</div>
Il peut arriver dâavoir besoin de les avoir autour du .element plutĂŽt quâĂ lâintĂ©rieur. (Je nâai pas de cas dâusages en tĂȘte mais on mâa posĂ© la question la semaine derniĂšre đ)
<div class="parent">
::before
<div class="element">
<!-- les enfants classiques -->
</div>
::after
</div>
Lâastuce est alors dâajouter une div autour de .element, de la mettre en display: contents et de dĂ©placer les :before et :after sur celle-ci:
<div class="parent">
<div class="element-outer">
::before
<div class="element">
<!-- les enfants classiques -->
</div>
::after
</div>
</div>
.element-outer {
display: contents;
}
.element-outer::before,
.element-outer::after {
content: 'đ';
display: block;
}
Ainsi, vu que display: contents fait disparaĂźtre la balise aux yeux du navigateur, vous vous retrouvez dans la mĂȘme situation que si ::before, .element et ::after Ă©taient des enfants directs du .parent.
Voici un exemple dâutilisation avec lequel vous pouvez jouer : https://codepen.io/julienpradet/pen/XWoXKRO
Pour conclure
Bref, display: contents est une chouette solution mais pas non plus une solution miracle. Et subgrid rĂ©glera beaucoup plus de problĂšme pour nous. Ca reste la meilleure que jâai trouvĂ© Ă un instant T.
Est-ce que vous auriez fait diffĂ©remmentâŻ? Je serais trĂšs intĂ©ressĂ© de comparer les diffĂ©rentes approches possibles. NâhĂ©sitez pas Ă me les envoyer sur les rĂ©seaux sociaux (Mastodon, LinkedIn ou Twitter).
En tout cas, si cet article vous a plu, sachez que jâessaye de publier un article par semaine autour du dĂ©veloppement web. Beaucoup dâentre eux sont orientĂ©s autour de la Web Performance qui se rapproche de mon activitĂ© de Freelance, mais vous nâĂȘtes pas Ă lâabri de sujets autour de lâintĂ©gration, du dĂ©veloppement front ou de lâorganisation dâĂ©quipe. đ
Si vous voulez suivre mes publications, il paraĂźt que j'ai un feed RSS, Mastodon et un Twitter.
Si vous pensez à d'autres méthodes que vous voudriez que je mette en place (pigeon voyageur, avion en papier, etc.), n'hésitez pas à me les proposer :)