De l’aléatoire en CSS ?

Ces derniers temps, un mini-projet me trottait en tête : un site qui propose des idées de choses rapides à dessiner. La liste serait établie au préalable par moi-même ou par des propositions externes, mais tout serait statique. Le cahier des charges me semblait suffisamment simple pour que je puisse me passer d’un langage dynamique à la fois côté serveur et client (ni PHP, ni JavaScript en somme). C’était sans compter que je voulais que les idées soient suggérées de manière aléatoire.

En y réfléchissant hier soir, je me suis demandé : « et si on pouvait générer de l’aléatoire en CSS ? » La réponse courte est « non », mais il existe également une réponse longue et elle m’a été donnée par le site CSS-Tricks : « Are There Random Numbers in CSS? ».

Attention, n’utilisez pas cette technique en production. Elle n’est ni accessible, ni très maintenable. Il s’agit avant tout de jouer avec CSS. Vous êtes prévenu‧e ! 😄

Le principe est assez simple : il consiste à superposer plusieurs label avec le même texte, les uns au-dessus des autres, puis à modifier leur z-index en boucle à l’infini grâce à une animation. Ainsi, le clic surviendra sur un label que le visiteur ne saura pas prévoir. Les label sont associés à des champs radio : il suffit alors d’adapter l’affichage en fonction du sélecteur input:checked.

Ce qui est marrant avec cette méthode, c’est que la génération d’aléatoire est déplacée de l’ordinateur au visiteur.

Pour ma part, j’ai adapté la solution parce qu’elle ne me convenait pas tout à fait : j’ai préféré me baser sur des liens avec ancre et le sélecteur associé, :target. En effet, en cliquant sur un lien avec l’attribut href="#1" par exemple, le sélecteur CSS #1:target devient actif. Voici une illustration :

<p class="idee-dessin" id="1">Un renard qui fait du surf</p>
<p class="idee-dessin" id="2">Une tomate amoureuse</p>
<p class="idee-dessin" id="3">Une pendule en retard à un rendez-vous</p>

<div class="conteneur-boutons-nouvelle-idee">
    <a class="bouton-nouvelle-idee" href="#1">Nouvelle idée ?</a>
    <a class="bouton-nouvelle-idee" href="#2">Nouvelle idée ?</a>
    <a class="bouton-nouvelle-idee" href="#3">Nouvelle idée ?</a>
</div>

Ainsi, le premier lien « Nouvelle idée ? » active le premier paragraphe, etc. Il suffit alors de cacher en CSS tous les paragraphes, sauf celui visé par le sélecteur :target.

.idee-dessin {
    display: none;
}

.idee-dessin:target {
    display: block;
}

Il ne reste alors plus qu’à superposer les liens et animer leur z-index :

.conteneur-boutons-nouvelle-idee {
    position: relative;
}

@keyframes changeOrdre {
    from { z-index: 3; }
    to { z-index: 1; }
}

.bouton-nouvelle-idee {
    animation: changeOrdre 3s infinite linear;
    position: absolute;
}

.bouton-nouvelle-idee:nth-of-type(1) { animation-delay: -0.0s; }
.bouton-nouvelle-idee:nth-of-type(2) { animation-delay: -0.5s; }
.bouton-nouvelle-idee:nth-of-type(3) { animation-delay: -1.0s; }

.bouton-nouvelle-idee:active {
    z-index: 4 !important;
}

La dernière règle est importante car les navigateurs génèrent un clic uniquement si l’élément qui a reçu l’évènement mousedown est le même qui reçoit l’évènement mouseup. Comme les z-index changent en permanence, ce n’est souvent pas le cas. Il faut donc forcer un peu les choses en mettant l’élément :active au premier plan. C’est expliqué dans l’article de CSS-Tricks avec une solution plus compliquée, mais qui fait l’économie d’un !important.

Une dernière chose me chagrinait avec cette méthode, c’est qu’il était compliqué d’ajouter de nouvelles phrases à la main. J’ai donc écrit un script Python pour générer les fichiers HTML et CSS à partir d’un fichier contenant des phrases.

Le code est sur Framagit, et le résultat sur frama.io.

Avec ça je me serai bien marré, mais la prochaine fois j’utiliserai quand même JavaScript, ce sera plus simple 😅