Pour la sortie de ce nouveau blog, il me fallait mettre en place un effet visuel renversant, qui impressionnerait la galerie et montrerait toute l’étendue de mes capacités.

Comme je n’avais pas le temps de faire cela, j’ai codé un effet rigolo de survol : lorsque la souris est passée sur ma photo, deux émojis parmi une centaine soigneusement sélectionnés apparaissent aléatoirement.

Vidéo : la souris survole ma photo. À chaque survol, deux émojis aléatoires recouvrent mes yeux.

Du grand art, je vous dis.

Cela a soulevé une question intéressante : quelle serait la meilleure méthode pour sélectionner l’émoji aléatoirement ? CSS suffirait-t-il ?

CSS : la roulette invisible

La roulette invisible, c’est le petit nom que je donne à cette technique que je n’ai pas inventée, mais qui est intéressante. Je vous explique.

Tout commence avec un pseudo-élément vide.

div::before {
  content: '';
}

Appliquons à ce pseudo-élément une animation emojis. Son but ? Changer le contenu de la propriété content pour y injecter nos émojis. Pour l’instant, trois suffiront.

div::before {
  content: '';
  animation: emojis 1s linear infinite;
}

@keyframes emojis {
  0% {
    content: "💥" / "";
  }

  50% {
    content: "🐸" / "";
  }

  100% {
    content: "🎉" / "";
  }
}

La syntaxe content: "🎉" / "" permet de définir, dans la seconde string, le texte alternatif pour ce pseudo-élément, qui pourra être lu par les technologies d’assistance. Quand on utilise un pseudo-élément à des fins décoratives, il est donc recommandé d’utiliser une chaîne vide pour éviter que le contenu de l’émoji ne soit interprété, par exemple par un lecteur d’écran.

Et voici le résultat :

Voir le code sur CodePen.

Maintenant, la même chose, mais avec 100 émojis ! Bon, on s’amuse bien mais on va utiliser une boucle SASS, on a pas que ça à faire...

$emojis: "💥", "💋", "🫀", "👓", "🐸", "👑", "🐰", "🐼", "👋", "🐤", "🐱", "🦊", "🐷", "🙈", "🐝", "🐌", "🐞", "🐠", "🐢", "🐫", "🦔", "🦚", "🌹", "🌼", "⭐️", "🔥", "👀", "🌈", "💧", "🍉", "🍓", "🍑", "🥝", "🍆", "🥦", "🥨", "🧀", "🍔", "🍕", "🍙", "🎂", "🍭", "🍿", "🍩", "🍺", "🍹", "🏀", "🥋", "🏆", "🎟", "🎭", "🎨", "🎬", "🎹", "🥁", "🎷", "🎸", "🪗", "🎲", "🎯", "🎰", "🎳", "🚨", "🚇", "🚀", "🛸", "🛟", "🗺", "⛱", "🌋", "💻", "🖨", "💾", "🕹", "💿", "📼", "📸", "📽", "📠", "📺", "🧭", "⏰", "⏳", "💡", "💵", "💰", "💎", "🔮", "🧬", "🧻", "🎁", "🎈", "🎉", "🪩", "📫", "📚", "🔎", "🩵", "💯", "🔔", "👁‍🗨";

@keyframes emojis {
  @for $i from 1 through list.length($emojis) {
    #{($i - 1) * (100% / (list.length($emojis) - 1))} {
      content: list.nth($emojis, $i) / "";
    }
  }
}

Et quand 100 émojis défilent en quelques secondes seulement, eh bien ça donne ça :

Voir le code sur CodePen.

Le plus dur est fait. L’astuce réside dans le fait d’arrêter cette roulette infernale au survol.

div:hover::before {
  animation-play-state: paused;
}
L’élément concerné sur le site étant un lien, je peux aussi appliquer l’effet lorsque ce dernier obtient le focus. Une personne naviguant au clavier « profitera » ainsi de l’effet.

Survolez l’élément : l’animation est arrêtée net, et un emoji est ainsi « sélectionné ».

Voir le code sur CodePen.

Vous l’aurez compris, l’aléatoire ici réside dans l’action de survol elle-même, et plus précisément dans son timing.

La touche finale, c’est bien sûr de cacher le pseudo-élément jusqu’à ce que le survol ait lieu :

div::before {
  opacity: 0;
}

div:hover::before {
  opacity: 1;
  animation-play-state: paused;
}

Notre roulette invisible est prête !

Voir le code sur CodePen.

En appliquant les mêmes règles au pseudo-élément after et quelques tests de positionnement plus tard, le résultat est là.

Invisible... et pourtant, elle tourne

Même si je trouve cette technique intéressante à partager, j’étais un peu gêné une fois la mise en place effectuée.

C’est qu’il y a quelque chose d’un peu contre-nature à changer de très nombreuses fois par seconde un contenu qu’on ne voit pas, non ?

Faisons une analyse de performance de la page, sur une minute, pour bien voir.

Analyse de performance de la démo. On y voit d’innombrables barres verticales symbolisant les opérations.
Chaque barre verticale est une opération sollicitant la carte graphique.

Le navigateur est au repos 99% du temps. Pas la fin du monde, donc. Mais ces milliers de petites barres vertes, ce sont des milliers d’opérations parfaitement inutiles, exécutées en permanence. Sur le principe, ça me gêne !

C’est un peu l’équivalent de préparer une pizza, de la jeter directement à la poubelle puis d’en préparer une autre, et ainsi de suite jusqu’à ce qu’un client ait enfin envie d’en profiter.

Alors, pour alléger un peu cette technique de gros bourrin, il y a une piste. Au lieu d’animer la propriété content, nous pouvons animer le contenu d’une variable CSS --emoji, et nous en servir pour générer le content uniquement lorsque l’élément est survolé.

div:hover::before {
  opacity: 1;
  animation-play-state: paused;
  content: var(--emoji);
}

@keyframes emojis {
  @for $i from 1 through list.length($emojis) {
    #{($i - 1) * (100% / (list.length($emojis) - 1))} {
      --emoji: "#{list.nth($emojis, $i)}" / "";
    }
  }
}

Résultat ?

Analyse de performance de la démo ajustée. Il y a moins de barres verticales.
La carte graphique est beaucoup moins sollicitée, mais il y a toujours énormément d’opérations.

Côté performance, c’est maintenant à 99,6% du temps que le navigateur est au repos. Beaucoup mieux !

Mais, fondamentalement, cela revient au même : le navigateur doit suivre l’état de cette variable en permanence, provoquant des milliers d’opérations superflues. J’aimerais savoir s’il est possible d’obtenir une version « propre » de cette approche. Mais j’en doute.

La solution raisonnable

Mon cerveau est câblé depuis longtemps pour commencer par envisager une solution purement CSS à ce genre de problème. Même quand ça ne marche pas, je découvre toujours quelque chose.

Mais ici, c’est bien JavaScript qui nous apporte une solution simple et performante. Le code est sans grande surprise, en voici la partie principale :

const emojis = ["💥", "💋", "🫀", "👓", "🐸", "👑", "🐰", "🐼", ...];

function setRandomEmoji() {
  $target.style.setProperty(
    "--emoji",
    `"${emojis[Math.floor(Math.random() * emojis.length)]}" / ""`
  );
}

$target.addEventListener("mouseenter", setRandomEmoji);

Plus d’opérations inutiles !

Certes, CSS reste à privilégier lorsque c’est possible. Mais il ne faut surtout pas rester bloqué sur un langage ou un autre lorsqu’il devient évident qu’une meilleure solution est à chercher ailleurs.

CSS n’a pas dit son dernier mot

Et Houdini, alors ? Cette API native permet de définir, en JavaScript, des algorithmes de rendus invoquables par la suite en CSS.

Et effectivement, dans ce contexte-ci, l’aléatoire est possible !

Voici la définition d’une règle circle, qui va générer aléatoirement un cercle rouge ou vert.

registerPaint('circle', class {
  paint(ctx, geom) {
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);

    ctx.fillStyle = Math.random() > 0.5 ? "red" : "green";

    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI);
    ctx.fill();
  }
});

On l’utilise ainsi :

div:hover {
  background: paint(circle);
}

Sympathique, car on laisse le navigateur s’occuper de la logique de survol, et les possibilités ouvertes par Houdini sont bien plus vastes que mon simple exemple.

Malheureusement, ni Firefox ni Safari ne supportent cette API à l’heure actuelle. Si vous êtes sur un navigateur compatible, vous pourrez voir le résultat en survolant le composant ci-dessous :

Voir le code sur CodePen.

Cela doit faire 5 ans que j’ai entendu parler d’Houdini, mais j’ai l’impression que le projet peine à se concrétiser pleinement. En tout cas, il semble avancer lentement. Il faut dire qu’un travail énorme a été effectué ces dernières années sur des aspects bien plus fondamentaux de CSS !

Pour ma petite animation, le support limité d’Houdini passe encore : c’est une fonctionnalité « bonus » dont personne ne souffrira de l’absence.

Bien plus embêtant, impossible de trouver comment rendre du texte, et encore moins un émoji. On dirait bien que cela est impossible...

random-item() en CSS : c’est prévu !

Les choses sont bien faites. Alors que je réfléchissais à cet article, je suis tombé sur cet article d’Alvaro Montoro, qui détaille les nouveautés à venir concernant les valeurs et les fonctions en CSS (le niveau 5 du module Values and Units).

Et vous l’avez compris : parmi tout un tas de fonctionnalités très prometteuses se trouve la tant fantasmée fonction random-item().

Voici à quoi pourrait ressembler mon code mis à jour :

div:hover:before {
  content: random-item(--x, "💥", "💋", "🫀", "👓", "🐸", "👑",...);
}
Si j’ai bien compris, le --x servira à « identifier » le générateur. Tous les appels à random-item() possédant le même identifiant et la même liste génèreront la même valeur sur la page.

Plus simplement, la fonction random() permettra d’obtenir une valeur aléatoire entre deux bornes :

div {
  width: random(0vw, 100vw);
}

Encore une très bonne nouvelle pour CSS, donc.

Quand cela sera-t-il disponible ? Pour nous autres mortels, cela aussi relève du domaine de l’aléatoire...

Cet article lance ce nouveau site !

Et vous pourrez suivre ses futures publications principalement via ce bon vieux flux RSS ou mon compte Mastodon.

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.