X

Résolu avec :has() : espacement vertical dans le texte long | Astuces CSS


Si vous avez déjà travaillé sur des sites contenant beaucoup de texte long, en particulier des sites CMS où les utilisateurs peuvent saisir des chapes de texte dans un éditeur WYSIWYG, vous avez probablement dû écrire du CSS pour gérer l’espacement vertical entre différents éléments typographiques, comme titres, paragraphes, listes, etc.

Il est étonnamment difficile de bien faire les choses. Et c’est l’une des raisons pour lesquelles des choses comme le plugin Tailwind Typography et Stack Overflow’s Prose existent – bien qu’elles gèrent bien plus que l’espacement vertical.

Firefox prend en charge :has() derrière la layout.css.has-selector.enabled drapeau dans about:config au moment de la rédaction.

Qu’est-ce qui complique l’espacement vertical typographique ?

Cela devrait sûrement être aussi simple que de dire que chaque élément – p, h2, ul, etc. – a une certaine quantité de marge supérieure et/ou inférieure… n’est-ce pas ? Malheureusement, ce n’est pas le cas. Considérez ce comportement souhaité :

  • Le premier et le dernier élément d’un bloc de texte long ne doivent pas avoir d’espace supplémentaire au-dessus ou au-dessous (respectivement). C’est ainsi que d’autres éléments non typographiques sont toujours placés de manière prévisible autour du contenu de forme longue.
  • Les sections du contenu long doivent avoir un grand espace entre elles. Une “section” étant un titre et tout le contenu suivant qui appartient à ce titre. En pratique, cela signifie avoir un beau grand espace avant un cap… mais pas si cette rubrique est immédiatement précédée d’une autre rubrique !
Nous voulons plus d’espace au-dessus du titre 3 lorsqu’il suit un élément typographique, comme un paragraphe, mais moins d’espace lorsqu’il suit immédiatement un autre titre.

Vous n’avez pas besoin de chercher plus loin qu’ici à CSS-Tricks pour voir où cela pourrait être utile. Voici quelques captures d’écran d’espacement que j’ai extraites d’un autre article.

L’espacement vertical entre le titre 2 et le titre 3
L’espace vertical entre le titre 3 et un paragraphe

La solution traditionnelle

La solution typique que j’ai vue consiste à mettre tout contenu long dans un emballage div (ou une balise sémantique, le cas échéant). Mon nom de classe préféré a été .rich-text, que je pense utiliser comme une gueule de bois des anciennes versions du CMS Wagtail, qui ajouterait automatiquement cette classe lors du rendu du contenu WYSIWYG. Tailwind Typography utilise un .prose class (plus quelques classes de modificateurs).

Ensuite, nous ajoutons CSS pour sélectionner tous les éléments typographiques dans ce wrapper et ajouter des marges verticales. En notant, bien sûr, le comportement spécial mentionné ci-dessus concernant les en-têtes empilés et le premier/dernier élément.

La solution traditionnelle semble raisonnable… quel est le problème ? Je pense qu’il y en a quelques…

Structure rigide

Avoir à ajouter une classe wrapper comme .rich-text aux bons endroits signifie cuire dans une structure spécifique à votre code HTML. C’est parfois nécessaire, mais il semble que cela ne devrait pas être le cas dans ce cas particulier. Il peut également être facile d’oublier de le faire partout où vous en avez besoin, surtout si vous devez l’utiliser pour un mélange de CMS et de contenu codé en dur.

La structure HTML devient encore plus rigide lorsque vous souhaitez pouvoir couper les marges supérieure et inférieure des premier et dernier éléments, respectivement, car ils doivent être des enfants immédiats de l’élément wrapper, par exemple, .rich-text > *:first-child. Ce > est important – après tout, nous ne voulons pas sélectionner accidentellement le premier élément de la liste dans chaque ul ou ol avec ce sélecteur.

Propriétés de la marge de mélange

Dans le pré-:has() monde, nous n’avons pas eu le moyen de sélectionner un élément en fonction de ce qui le suit. Par conséquent, l’approche traditionnelle de l’espacement des éléments typographiques consiste à utiliser un mélange des deux margin-top et margin-bottom:

  1. Nous commençons par définir notre espacement par défaut aux éléments avec margin-bottom.
  2. Ensuite, nous espacerons nos “sections” en utilisant margin-top — c’est-à-dire un très grand espace au-dessus de chaque titre
  3. Ensuite, nous remplaçons ces gros margin-tops lorsqu’un en-tête est suivi immédiatement d’un autre en-tête à l’aide du sélecteur de frère adjacent (ex. h2 + h3).

Maintenant, je ne sais pas pour vous, mais j’ai toujours pensé qu’il était préférable d’utiliser une seule direction de marge lors de l’espacement des choses, favorisant généralement margin-bottom (c’est en supposant que le CSS gap la propriété n’est pas faisable, ce qui n’est pas le cas dans ce cas). Que ce soit un gros problème, ou même vrai, je vous laisse décider. Mais personnellement, je préférerais mettre margin-bottom pour espacer le contenu de forme longue.

Effondrement des marges

En raison de l’effondrement des marges, ce mélange de marges supérieure et inférieure n’est pas un gros problème en soi. Seule la plus grande des deux marges empilées prendra effet, et non la somme des deux marges. Mais… eh bien… je n’aime pas vraiment les marges qui s’effondrent.

L’effondrement des marges est encore une chose dont il faut être conscient. Cela peut être déroutant pour les développeurs juniors qui ne sont pas au courant de cette bizarrerie CSS. L’espacement changera totalement (c’est-à-dire cessera de s’effondrer) si vous deviez changer l’emballage en un flex mise en page avec flex-direction: column par exemple, ce qui n’arriverait pas si vous réglez vos marges verticales dans une seule direction.

Je sais plus ou moins comment fonctionnent les marges qui s’effondrent, et je sais qu’elles sont là par conception. Je sais aussi qu’ils m’ont parfois facilité la vie. Mais ils ont aussi rendu les choses plus difficiles à d’autres moments. Je pense juste qu’ils sont un peu bizarres, et je préfère généralement éviter de compter sur eux.

Le :has() solution

Et voici ma tentative de résoudre ces problèmes avec :has().

Pour résumer les améliorations que cela vise à apporter :

  • Aucune classe wrapper n’est requise.
  • Nous travaillons avec une direction de marge cohérente.
  • Les marges qui s’effondrent sont évitées (ce qui peut ou non être une amélioration, selon votre position).
  • Il n’y a pas de styles de réglage pour ensuite les remplacer immédiatement.

Remarques et mises en garde sur le :has() solution

  • Vérifiez toujours la prise en charge du navigateur. Au moment de la rédaction, Firefox ne prend en charge que :has() derrière un drapeau expérimental.
  • Ma solution n’inclut pas tous les éléments typographiques possibles. Par exemple, il n’y a pas <blockquote> dans ma démo. La liste de sélection est cependant assez facile à étendre.
  • Ma solution ne gère pas non plus les éléments non typographiques qui peuvent être présents dans vos blocs de texte longs particuliers, par exemple <img>. En effet, pour les sites sur lesquels je travaille, nous avons tendance à verrouiller autant que possible le WYSIWYG sur les nœuds de texte de base, comme les titres, les paragraphes et les listes. Tout le reste – par exemple, des citations, des images, des tableaux, etc. – est un bloc de composant CMS séparé, et ces blocs eux-mêmes sont espacés les uns des autres lorsqu’ils sont rendus sur une page. Mais encore une fois, la liste de sélection peut être étendue.
  • j’ai seulement inclus h1 par souci d’exhaustivité. Je n’autoriserais généralement pas un utilisateur du CMS à ajouter un h1 via WYSIWYG, car le titre de la page serait intégré quelque part dans le modèle de page plutôt que saisi dans l’éditeur de page CMS.
  • Je ne traite pas d’un titre suivi immédiatement du même titre de niveau (h2 + h2). Cela signifierait que le premier titre ne « posséderait » aucun contenu, ce qui semble être une mauvaise utilisation des titres (et, corrigez-moi si je me trompe, mais cela pourrait violer WCAG 1.3.1 Informations et relations). Je ne traite pas non plus les niveaux de titre sautés, qui ne sont pas valides.
  • Je ne touche en rien aux approches existantes que j’ai mentionnées. Si et quand je construirai un autre site Tailwind, j’utiliserai l’excellent plugin Typography, sans aucun doute !
  • Je ne suis pas designer. Je suis venu avec ces valeurs d’espacement en le regardant. Vous pourriez (et devriez) probablement utiliser de meilleures valeurs.

Spécificité et structure du projet

J’allais écrire tout un article ici sur la façon dont la méthode traditionnelle et la nouvelle :has() façon de faire pourrait s’inscrire dans ITCSS méthodologie… Mais maintenant que nous avons :where() (le sélecteur de spécificité zéro), vous pouvez à peu près choisir votre niveau de spécificité préféré pour n’importe quel sélecteur maintenant.

Cela dit, le fait que nous n’ayons plus affaire à un emballage — .prose, .rich-text, etc. – pour moi, cela donne l’impression que cela devrait vivre dans la couche “éléments”, c’est-à-dire avant de commencer à traiter la spécificité au niveau de la classe. j’ai utilisé :where() dans mes exemples pour garder la spécificité cohérente. Tous les sélecteurs dans mes deux exemples ont un score de spécificité de 0,0,1 (sauf pour la réinitialisation de base).

Emballer

Alors voilà, une solution de pointe à un problème très ennuyeux ! Cette nouvelle approche n’est toujours pas ce que j’appellerais un CSS “simple” – comme je l’ai dit au début, c’est un sujet plus complexe qu’il n’y paraît au premier abord. Mais en plus d’avoir quelques sélecteurs légèrement complexes, je pense que la nouvelle approche a plus de sens dans l’ensemble, et la structure HTML moins rigide semble très attrayante.

Si vous finissez par utiliser ceci, ou quelque chose comme ça, j’aimerais savoir comment cela fonctionne pour vous. Et si vous pouvez penser à des façons de l’améliorer, j’aimerais les entendre aussi !