Lancer de dé
Un dé en 3D qui roule pour de vrai, en CSS pur, avec l'historique des derniers lancers. Le bon exercice pour comprendre la perspective et les transformations.
- Construire un cube avec preserve-3d
- Tourner jusqu'à la bonne face
- Attendre la fin d'une transition avec transitionend
Pourquoi un dé ? Parce qu'il réunit trois choses qu'on sépare trop souvent : un tirage au hasard, une animation CSS en 3D, et le moment précis où le résultat s'affiche.
Le secret : le hasard décide d'abord, l'animation ne fait que montrer le résultat. Jamais l'inverse.
Pourquoi en CSS 3D ? Un cube, six faces, une rotation. Le navigateur fait tout le calcul de perspective.
LE HTML
Un cube de six faces dans une scène. Les faces opposées font toujours 7.
<div class="scene"> <div class="de" id="de"> <div class="face f1">1</div><div class="face f2">2</div><div class="face f3">3</div> <div class="face f4">4</div><div class="face f5">5</div><div class="face f6">6</div> </div> </div> <button id="lancer">Lancer</button> <output id="resultat"></output>LE CUBE EN CSS
Chaque face est tournée, puis avancée de la moitié de la taille du cube.
.scene { perspective: 600px; } .de { width: 100px; height: 100px; position: relative; transform-style: preserve-3d; transition: transform 1.2s cubic-bezier(.2, .8, .2, 1); } .face { position: absolute; inset: 0; display: grid; place-items: center; } .f1 { transform: rotateY(0deg) translateZ(50px); } .f2 { transform: rotateY(90deg) translateZ(50px); } .f3 { transform: rotateX(90deg) translateZ(50px); } .f4 { transform: rotateX(-90deg) translateZ(50px); } .f5 { transform: rotateY(-90deg) translateZ(50px); } .f6 { transform: rotateY(180deg) translateZ(50px); }L'ORIENTATION DE CHAQUE FACE
Pour montrer une face, on tourne le cube dans le sens inverse de cette face.
const ORIENT = { 1: [0, 0], 2: [0, -90], 3: [-90, 0], 4: [90, 0], 5: [0, 90], 6: [0, 180] };LANCER
Le résultat est tiré d'abord. On ajoute deux tours complets pour que le cube tourne toujours.
let tours = 0; const de = document.getElementById('de'); const sortie = document.getElementById('resultat'); document.getElementById('lancer').addEventListener('click', () => { const valeur = 1 + crypto.getRandomValues(new Uint32Array(1))[0] % 6; tours += 2; const [x, y] = ORIENT[valeur]; sortie.textContent = ''; de.addEventListener('transitionend', () => { sortie.textContent = valeur; }, { once: true }); de.style.transform = `rotateX(${x + tours * 360}deg) rotateY(${y + tours * 360}deg)`; });
PIÈGE 1 : Laisser l'animation décider
Mauvais : faire tourner le dé au hasard, puis deviner quelle face se retrouve devant. Les calculs d'angles deviennent un cauchemar.
Correct : tirer le résultat d'abord, puis tourner vers l'orientation connue de cette face.
PIÈGE 2 : Un setTimeout calé sur la durée
Mauvais : afficher le résultat avec setTimeout(..., 1200). Si quelqu'un change la durée dans le CSS, le chiffre arrive trop tôt ou trop tard.
Correct : écouter transitionend, avec { once: true } pour ne pas empiler les écouteurs.
PIÈGE 3 : Le dé qui ne tourne plus
Mauvais : relancer et retomber sur la même face. L'angle ne change pas, il n'y a aucune animation, et transitionend n'arrive jamais.
Correct : ajouter des tours complets à chaque lancer (tours += 2) : l'angle change toujours.
- Des points au lieu des chiffres : une grille 3 × 3 par face, avec seulement les cases utiles remplies.
- Plusieurs dés : une boucle qui crée n cubes et additionne les résultats.
- Vos couleurs : une variable CSS --face et --chiffre par thème.
- Avec prefers-reduced-motion, coupez la transition et affichez le résultat tout de suite (sinon transitionend n'arrive jamais).
- Annoncez le résultat aux lecteurs d'écran avec aria-live sur la sortie.
La valeur et le nombre de tours vont dans un useState, la rotation se calcule pendant le rendu.
onTransitionEnd sur le cube affiche le résultat, sans setTimeout.
Chargement des commentaires…