La Cascade

Un peu de tout sur CSS, HTML, SVG et JS,traduit du web anglophone
Rechercher

Plongée en profondeur dans les pseudo-éléments :before et :after

par Will Boyd, 13 février 2023, css, pseudo-elements, animation, accessibilite, article original paru le 22 février 2021 dans Codersblock

Tout ce qu'on peut faire avec les pseudo-éléments ::before et ::after. Will Boyd montre un tas de solutions pratiques facilement applicables.


NdT : Pour une intro générale, vous pouvez consulter Les pseudo-éléments CSS3 :before et :after

Les pseudo-éléments ::before et ::after sont des outils incroyablement polyvalents dans la boîte à outils CSS. Leur compréhension peut vous aider à élaborer des CSS pratiques pour résoudre toutes sortes de situations. Vous pouvez également aller plus loin et les utiliser pour créer d'impressionnantes prouesses en matière d'astuces CSS.

Cet article commence par le rez-de-chaussée, en expliquant ces pseudo-éléments et comment les utiliser. Nous nous plongerons dans des sujets plus excitants au fil de l'article. Nous parlerons également beaucoup de la propriété CSS content, car elle est très liée.

deux moineaux regardant, l'un à gauche, l'autre à droite

Une présentation rapide de ::before et ::after

En CSS, ::before et ::after sont des mots-clés que vous pouvez ajouter à un sélecteur pour créer des pseudo-éléments. Les pseudo-éléments sont insérés dans le ou les éléments correspondant au sélecteur, avant ou après tout contenu.

Le plus simple est probablement de montrer un exemple. Prenez le HTML et le CSS suivants.

<p class="my-paragraph">I'm a paragraph!</p>
.my-paragraph {
  padding: 20px;
  border: 4px dashed lightgray;
}

.my-paragraph::before {
  content: 'This is before!';
  color: hotpink;
}

.my-paragraph::after {
  content: 'This is after!';
  color: steelblue;
}

Si vous jetez un coup d'œil aux outils de développement, vous verrez ::before et ::after à l'intérieur de l'élément <p>. Vous pouvez cliquer dessus pour afficher ou modifier leurs styles, comme tout autre élément.

devtool permet de voir before et after à l'intérieur de l'élément p

Nous venons d'utiliser CSS pour créer des éléments et du contenu qui n'existaient pas dans le HTML. Trop fort !

Lignes d'en-tête décoratives

::before et ::after sont parfaits pour ajouter une décoration. Regardez ce titre avec des lignes décoratives.

voir Decorative Lines Heading de lonekorean dans CodePen

Le HTML consiste juste en une seule balise <h1>.

<h1>Elegant Heading</h1>

Tout se passe dans le CSS :

h1 {
  display: grid;
  grid-template-columns: minmax(50px, 1fr) auto minmax(50px, 1fr);
  align-items: center;
  text-align: center;
  gap: 40px;
}

h1::before,
h1::after {
  content: '';
  border-top: 6px double;
}

Avec ces quelques ligne de CSS, on insère des pseudo-éléments avant et après le texte du titre. Leur content a pour valeur '', car nous ne voulons pas réellement de contenu, nous voulons simplement des éléments à cet endroit afin de pouvoir leur appliquer des doubles bordures pour créer les lignes.

C'est le bon moment pour mentionner que la propriété content est requise pour qu'un pseudo-élément soit créé, mais comme vous le voyez ici, on peut très bien la régler sur '' (chaîne vide).

La mise en page est réalisée en transformant l'élément <h1> en 3 colonnes Grid. Les colonnes de gauche et de droite sont des lignes doubles, toutes deux avec une largeur de minmax(50px, 1fr), ce qui signifie que leur largeur sera au minimum de 50 px. Le texte du titre est proprement centré dans la colonne centrale.

Ruban d'en-tête décoratif

Bon, poussons ce concept plus loin. Voici un titre en forme de ruban.

voir Decorative Ribbon Heading de lonekorean dans CodePen

Là encore, tout ce dont nous avons besoin est une seule balise

dans le HTML.

<h1>Ribbon Heading</h1>
h1 {
  position: relative;
  margin: 0 auto 20px;
  padding: 10px 40px;
  text-align: center;
  background-color: #875e46;
}

h1::before,
h1::after {
  content: '';
  width: 80px;
  height: 100%;
  background-color: #724b34;

  /* positionner les bouts du ruban en arrière et légèrement plus bas */
  position: absolute;
  z-index: -1;
  top: 20px;

  /* clip ribbon end shape */
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 25% 50%);

  /* draw and position the folded ribbon bit */
  background-image: linear-gradient(
    45deg,
    transparent 50%,
    #5d3922 50%
  );
  background-size: 20px 20px;
  background-repeat: no-repeat;
  background-position: bottom right;
}

h1::before {
  left: -60px;
}

h1::after {
  right: -60px;
  transform: scaleX(-1); /* flip horizontally */
}

Cette fois, nous utilisons le positionnement absolu pour placer les pseudo-éléments. L'extrémité gauche du ruban est créée avec ::before et la droite avec ::after. Le CSS les dessine ensemble avec des styles partagés, dans la même direction, puis retourne h1::after avec transform : scaleX(-1).

La forme des extrémités du ruban est obtenue avec clip-path. Les bouts de ruban pliés sont de petits triangles d'image de fond dessinés avec linear-gradient() et soigneusement positionnés dans les coins intérieurs inférieurs.

Travailler avec des guillemets

Revenons au réglage du contenu de ::before et de ::after sur autre chose qu'une chaîne vide, car il y a des trucs intéressants à faire.

Le contenu peut être défini sur les valeurs spéciales open-quote et close-quote. Ça permet d'insérer des guillemets ouverts/fermés appropriés à la langue actuelle. Le style appliqué à un <blockquote> est un bon cas d'utilisation pour ça.

<blockquote lang="en">
  Hello! I am a block quote in English. Check out how the quotation
  marks are automatically added around me!
</blockquote>

<blockquote lang="fr">
  Salut ! Je suis une citation en français. Je ne connais pas le
  français, alors j’ai demandé à quelqu’un sur Twitter de le traduire
  !
</blockquote>
blockquote::before {
  content: open-quote;
}

blockquote::after {
  content: close-quote;
}

blockquote::before,
blockquote::after {
  opacity: 0.25;
  padding: 0 10px;
  font-size: 3em;
}

blockquote {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 20px;
  padding: 20px 10px;
  border-radius: 10px;
  background-color: #e5ddcb;
}

Nous avons maintenant des guillemets stylisés automatiquement pour <blockquote>.

voir Stylish Blockquotes de lonekorean dans CodePen

Contrairement à <blockquote>, la balise <q> (citation en ligne) affiche des guillemets par défaut. Ce n'est pas de la magie — c'est la feuille de style de l'agent utilisateur par défaut de votre navigateur qui fait la même chose que nous venons de faire !

q::before {
  content: open-quote;
}

q::after {
  content: close-quote;
}

Une autre caractéristique intéressante des balises open-quote et close-quote est qu'elles tiennent compte de l'imbrication. Par exemple, en anglais, si vous avez des balises <q> imbriquées, la balise extérieure utilisera les guillemets doubles et tandis que la balise intérieure utilisera des guillemets simples et .

La propriété CSS quotes vous donne un contrôle direct sur ce qui est utilisé pour open-quote et close-quote.

q {
  quotes: '<' '>'; /* wrap all quotes with < and > */
}

q {
  quotes: '<' '>' /* wrap outermost quotes with < and > */ '<<' '>>'
    /* then quotes nested one level deep with << and >> */ '<<<' '>>>'; /* then quotes nested deeper with <<< and >>> */
}
voir Open/Close Quotation Marks de lonekorean dans CodePen

Chaque citation a le même balisage...

<q>If you're fine with <q>unexplained phenomena</q> then go ahead.</q>

...mais des valeurs différentes de `quote'.

.customized {
  quotes: '→' '←';
}

.customized-nested {
  quotes: '⟪' '⟫' '⟨' '⟩';
}

.emoji {
  quotes: '😀💬"' '"';
}

q::before,
q::after {
  color: #ff003c;
}

NdT : pour en savoir plus sur le sujet des citations en HTML, vous pouvez consulter Citer en HTML ici-même.

Afficher des attributs d'éléments

Les attributs des éléments peuvent être affichés dans les pseudo-éléments ::before et ::after en utilisant attr(). Par exemple, href est un attribut de l'élément <a> ci-dessous...

<a href="https://tacobell.com">Taco Bell</a>

...et nous pouvons afficher la valeur de href dans le lien avec ceci.

a::after {
  content: ' → ' attr(href); /* show an arrow before the href */
}

Voici ce que ça peut donner :

voir Displaying Link URLs de lonekorean dans CodePen

J'ai glissé quelque chose de plus dans le CSS ci-dessus. Vous pouvez mettre plusieurs choses dans le content (généralement séparées par un espace blanc) pour les concaténer. Dans ce cas, ' → ' est concaténé avec attr(href) pour obtenir ce que vous voyez dans la démo.

Afficher le compte avec Counters

CSS counters vous permet de, eh bien, compter des choses. Les valeurs de comptage peuvent être affichées à l'aide de ::before et ::after. Regardez cette démo qui affiche le nombre de cases à cocher cochées. Il n'y a pas de JavaScript impliqué.

voir Checked Checkboxes Counter de lonekorean dans CodePen

Voici le CSS correspondant.

input:checked {
  counter-increment: total;
}

output::before {
  content: counter(total);
}

Chaque case cochée incrémente le compteur total, qui est ensuite affiché dans le pseudo-élément ::before de l'élément <output>.

Si vous souhaitez en savoir plus sur les compteurs CSS, consultez cet article.

Jouer avec les images

Vous pouvez placer une image dans ::before ou ::after en définissant à content une url.

div::before {
  content: url(image.png);
}

Vous vous rappelez la concaténation de content de tout à l'heure ? Elle fonctionne également avec les images ! Voici un exemple qui concatène une image, une chaîne de caractères et un attribut.

<div data-lives="3"></div>
div::before {
  content: url(mario.gif) ' × ' attr(data-lives);
}
voir Mario Lives Screen de lonekorean dans CodePen

Vous n'êtes pas obligé d'utiliser content pour afficher une image. Une autre option consiste à utiliser background-image avec un pseudo-élément de taille appropriée.

div::before {
  content: '';
  background-image: url(image.png);
  display: block;
  width: 100px;
  height: 100px;
}

Il vaut la peine de noter que ::before et ::after ont par défaut la valeur display : inline. Vous devrez modifier cela si vous souhaitez définir la largeur/hauteur, comme vous pouvez le voir dans le CSS ci-dessus.

Remplacement du contenu

Comme vous l'avez déjà remarqué, ::before et ::after dépendent de content. Ils ne s'afficheront pas si content n'est pas défini. Mais la dépendance n'est pas réciproque — il existe des situations où content peut être utilisé sans ::before ou ::after.

Si content a pour valeur une seule image, vous pouvez l'utiliser directement sur un élément pour remplacer le contenu HTML de cet élément.

.replace {
  content: url(replace.png);
}

Essayons-le sur le HTML suivant.

<span class="replace">This is text!</span>
<span class="replace"
  ><img src="chicken-nugget.png" alt="chicken nugget"
/></span>
<img class="replace" src="chicken-nugget.png" alt="chicken nugget" />

Le HTML teste 3 cas.

  1. Un élément avec un texte simple. Il sera remplacé.
  2. Un élément avec <img> à l'intérieur. Il sera également remplacé.
  3. Un élément <img> directement. Firefox ne le remplacera pas, mais d'autres navigateurs le feront.

Voyez par vous-même !

voir Content Replacement de lonekorean dans CodePen

Pour clarifier, tout ce qui se trouve à l'intérieur de l'élément est remplacé. Vous pourriez donc remplacer une page entière par un nugget de poulet en faisant quelque chose comme ceci :

body {
  content: url(chicken-nugget.png);
}

(Veuillez utiliser ce pouvoir de manière responsable).

content et ::marker

::marker est un autre pseudo-élément qui peut utiliser du contenu, comme ::before et ::after. Il est utilisé pour styliser les marqueurs des éléments de liste. NdT : voir de bons exemples dans Des styles de listes créatifs.

<ul>
  <li>Cupcakes are essentially cakes, but cuppier.</li>
  <li>
    The first cupcake was invented by Harley McCupcakken in 1796.
  </li>
  <li>
    Alright fine, I don't actually know anything about cupcakes.
  </li>
</ul>
li::marker {
  content: '🧁';
}
voir Cupcake Markers with ::marker de lonekorean dans CodePen

Malheureusement, Safari ne vous permet pas de définir le contenu du ::marker (NdT : ce n'est plus tout à fait le cas, voir CanIUse), et les navigateurs qui l'autorisent ont encore d'autres restrictions.

Une autre option consiste à masquer entièrement le marqueur avec list-style-type : none, puis à utiliser ::before pour faire votre propre truc. Pour illustrer, j'ai recréé la démo précédente en utilisant ::before au lieu de ::marker.

Shenanigans !

Parfois, les gens veulent simplement être créatifs avec CSS. C'est amusant ! Un jeu particulièrement populaire consiste à voir tout ce que l'on peut faire avec un seul élément. ::before et ::after entrent très souvent en jeu ici, car le fait d'avoir ces pseudo-éléments supplémentaires à disposition ouvre de nombreuses possibilités.

  • La galerie A Single Div de Lynn Fisher contient quelques exemples impressionnants. Vérifiez le CSS et vous verrez beaucoup de pseudo-éléments ::before et ::after.
  • Pure CSS Flags, Single Divs de Dhanish Gajjar est une collection de 113 drapeaux du monde, dont beaucoup sont dessinés avec des pseudo-éléments ::before et ::after.
  • Et puis il y a les 700+ icônes CSS d'Astrit Malsija. Regardez de plus près une icône et, eh bien, vous comprenez l'idée.

Ces choses ne sont pas toujours pratiques, mais ce n'est pas le sujet. Il s'agit de s'amuser un peu et d'apprendre de nouvelles astuces en cours de route.

Contrôler la mise en page

Pour en revenir à des questions pratiques, ::after peut être utilisé avec des classes utilitaires pour contrôler la mise en page. Un exemple célèbre est le "clearfix", qui garantit qu'un élément enveloppe tous les éléments flottants qu'il contient. Voici un exemple d'implémentation, bien qu'il existe de nombreuses variantes.

.clearfix::after {
  content: '';
  display: block;
  clear: both;
}

Ce bout de CSS a été indispensable pendant de nombreuses années, mais il existe désormais une solution plus moderne.

.clearfix {
  display: flow-root;
}

Voici une démo illustrant le problème et les corrections. Remarquez comment le premier nugget de poulet s'étend en dehors de son élément parent, mais pas le deuxième ni le troisième.

voir Clearing Chicken Nuggets de lonekorean dans CodePen

Le pseudo-élément ::after peut également aider à la mise en page en insérant des sauts de ligne. Pour cela, je vous renvoie à l'astuce rapide d'Andy Bell.

Est-ce qu'on peut l'animer ?

Pouvez-vous appliquer des animations CSS à ::before et ::after ? Oui ! Elles fonctionnent exactement comme sur les éléments normaux.

Cela dit, l'animation de la propriété content est un peu plus intéressante. Dans les navigateurs autres que Safari, elle peut être animée de manière discrète, ce qui signifie que le contenu basculera entre les valeurs sans aucune sorte de transition graduelle.

div::before {
  content: '';
  animation: flip 6s linear infinite;
}

@keyframes flip {
  from {
    content: 'Hello!';
  }
  to {
    content: 'Goodbye!';
  }
}
voir Discretely Animated Text de lonekorean dans CodePen

Si vous souhaitez animer des nombres comptés dans du contenu, il existe une astuce, mais elle ne fonctionne que dans Chrome, Edge et Opera (ce sont les navigateurs qui prennent actuellement en charge l'API Propriétés et valeurs, sur laquelle repose cette astuce).

Voici comment cela fonctionne.

  1. Déclarez une propriété personnalisée qui est un nombre entier.
  2. Configurez un compteur pour prendre la valeur de ce nombre entier via counter-reset.
  3. Affichez ce compteur dans le contenu d'un pseudo-élément.
  4. Animez la valeur de l'entier.
@property --num {
  syntax: '<integer>';
  inherits: true;
  initial-value: 0;
}

div::before {
  counter-reset: my-counter var(--num);
  content: counter(my-counter);
  animation: count 10s ease-in-out infinite alternate;
}

@keyframes count {
  to {
    --num: 100;
  }
}
voir Number Counter Animation de lonekorean dans CodePen

Si vous souhaitez approfondir cette technique, consultez ce tutoriel qui va un peu plus loin.

Pseudo-éléments vs. pseudo-classes

Outre ::before et ::after, il existe d'autres pseudo-éléments qui sont utilisés pour styliser des parties d'éléments existants. Nous avons parlé de ::marker plus tôt, pour styliser les marqueurs d'éléments de liste. Un autre exemple est ::selection, qui est utilisé pour styliser le texte sélectionné.

Il existe également des pseudo-classes, qui sont différentes des pseudo-éléments. Les pseudo-classes appliquent des styles basés sur l'état. Par exemple, nous avons utilisé :checked dans une démo précédente pour appliquer un style uniquement lorsqu'une case à cocher était cochée.

Les pseudo-éléments sont préfixés par deux points (: :) alors que les pseudo-classes sont préfixées par un seul point ( :). Les anciennes versions de la spécification du W3C utilisaient des deux-points simples pour les pseudo-éléments, de sorte que vous pouvez voir occasionnellement :before et :after. Ils fonctionnent toujours, mais il est recommandé d'utiliser : : à la place.

Accessibilité

Il est intéressant de parler de la manière dont le contenu est transmis par les lecteurs d'écran. Selon la spécification du W3C, le contenu est destiné à être prononcé.

La propriété "content" s'applique à la parole et le contenu généré doit être rendu pour la sortie vocale.
CSS Generated Content Module Level 3

D'accord, ça a l'air bien. Mais regardons de plus près ce qui se passe dans différents cas.

Si vous utilisez ::before ou ::after à des fins décoratives et que content est défini comme '' (chaîne vide), les lecteurs d'écran ne les annonceront pas. Cela ne pose aucun problème !

div::after {
  /* le lecteur d'écran reste muet */
  content: '';
}

Les lecteurs d'écran lisent le texte placé dans content. Ça n'a pas toujours été le cas, mais ça l'est maintenant... à moins que vous ne supportiez encore IE11 (désolé). D'autres personnes ont partagé leurs résultats en testant diverses combinaisons navigateur + lecteur d'écran ici, ici et ici. Ce dernier semble mauvais au premier abord, jusqu'à ce que vous réalisiez que tous les échecs proviennent d'IE11 et de Firefox 29 (sorti en 2014).

div::after {
  /* le lecteur d'écran dit "hello" */
  content: 'hello';
}

N'oubliez pas que les emojis et les symboles comptent comme du texte. Les lecteurs d'écran les annonceront, ce qui peut être bien, mais peut aussi être gênant si vous les utilisez comme décoration.

div::after {
  /* le lecteur d'écran dit "yellow five pointed star" */
  content: '⭐';
}

div::after {
  /* le lecteur d'écran dit "clockwise open circle arrow" */
  content: '↻';
}

Les lecteurs d'écran peuvent reconnaître les images dans le contenu. Dans mes tests, VoiceOver annonçait "image" dans Firefox et Safari, et "image non étiquetée" dans Chrome.

div::after {
  /* le lecteur d'écran dit un truc comme "image" ou "unlabelled image" */
  content: url(image.png);
}

Si l'image n'est pas décorative, alors nous sommes dans une mauvaise passe. Nous avons une image significative qui n'est transmise que comme "image".

Si l'image est décorative, la personne qui utilise un lecteur d'écran ne manque peut-être rien d'important, mais elle ne le sait pas ! Elle entendra "image" et se demandera ce qu'elle rate.

La bonne nouvelle est que vous pouvez spécifier un texte alternatif pour le contenu. Cela nous donne un moyen d'indiquer aux lecteurs d'écran ce qu'il faut annoncer, de manière similaire à la définition de l'attribut alt sur <img>.

div::after {
  /* le lecteur d'écran dit "chicken nugget" */
  content: url(chicken-nugget.png) / 'chicken nugget';
}

div::after {
  /* le lecteur d'écran reste muet */
  content: url(decorative-image.png) / '';
}

Il fonctionne également avec du texte, de sorte que nous pouvons indiquer aux lecteurs d'écran comment annoncer les émojis et les symboles décoratifs.

div::after {
  /* le lecteur d'écran reste muet */
  content: '⭐' / '';
}

div::after {
  /* le lecteur d'écran dit "refresh" */
  content: '↻' / 'refresh';
}

La mauvaise nouvelle est que Firefox et Safari ne prennent pas en charge le texte alternatif pour le contenu. En fait, le / invalidera entièrement le contenu dans ces navigateurs. Vous pouvez contourner ce problème en déclarant le contenu deux fois. L'ordre est important !

div::after {
  content: '⭐'; /* Firefox and Safari will use this */
  content: '⭐' / ''; /* other browsers will use this */
}

Vous devez également savoir que le texte dans content est non sélectionnable et non cherchable, et que les images dans content ne peuvent pas faire l'objet d'un clic droit pour obtenir le menu contextuel de l'image. Ce n'est pas un gros problème pour des choses triviales ou décoratives, mais je réfléchirais avant de mettre du texte long ou des images importantes dans content.

Au revoir !

Il y a tellement de choses que vous pouvez faire avec les pseudo-éléments ::before et ::after et la propriété content. Je pense que c'est vraiment génial que vous puissiez avoir un balisage minimal — souvent une simple balise HTML — et que CSS puisse s'en servir et faire toutes ces choses étonnantes.

J'espère que nous avons couvert suffisamment de terrain dans cet article pour vous donner une idée des possibilités (et des limites !) et susciter un intérêt créatif.

Merci de votre lecture !

Autres ressources externes

Articles de Will Boyd traduits dans La Cascade

Voir la page de Will Boyd et la liste de ses articles dans La Cascade.
Article original paru le 22 février 2021 dans Codersblock
Traduit avec l'aimable autorisation de Codersblock et de Will Boyd.
Copyright Codersblock © 2021