Défi frontal accepté : CSS 3D Cube
Publié: 2022-03-10Vous aimez les défis ? Êtes-vous prêt à entreprendre une tâche que vous n'avez jamais rencontrée auparavant et à le faire dans les délais? Que faire si, dans l'exécution de la tâche, vous rencontrez un problème qui semble insoluble ? Je souhaite partager mon expérience d'utilisation des effets 3D CSS pour la première fois dans un projet réel et vous inspirer à relever des défis.
C'était un jour ordinaire quand Eugene, un manager de CreativePeople, m'a écrit. Il m'a envoyé une vidéo et m'a expliqué qu'il développait un concept pour un nouveau projet et se demandait s'il était possible pour moi de développer quelque chose comme ce qui était dans la vidéo.
Lectures complémentaires sur SmashingMag :
- Beercamp : une expérience avec CSS 3D
- Créer des formes réactives avec Clip-Path et sortir de la boîte
- Jouons avec le CSS accéléré par le matériel
C'était un objet 3D (un cuboïde, pour être précis) qui tournait autour d'un des axes. J'avais déjà une certaine expérience de travail avec CSS 3D, et une solution a commencé à se former dans mon esprit. J'ai cherché sur Google des mots-clés comme "CSS 3D cube" pour confirmer mes idées et j'ai répondu à Eugene que c'était possible.
La question suivante d'Eugene était de savoir si j'accepterais le projet ? J'aime les tâches délicates, donc je ne pouvais pas refuser. À l'époque, je ne savais pas dans quoi je m'embarquais, mais j'étais plus que déterminé.
Aiguisez vos haches
Rappelons-nous les axes - pas les axes de guerre, mais les droites numériques, les mêmes axes que dans le système de coordonnées cartésien tridimensionnel que nous avons étudié à l'école. Comme nous le dit Wikipédia :
Le système de coordonnées cartésien pour un espace tridimensionnel est un triplet ordonné de lignes (axes) qui sont perpendiculaires par paires, ont une seule unité de longueur pour les trois axes et ont une orientation pour chaque axe.
L'image ci-dessous montre comment les axes sont orientés dans un navigateur Web.

L'axe des x est horizontal, l'axe des y est vertical et l'axe des z semble sortir de l'écran vers vous. La valeur zéro de l'axe z est le plan de l'écran. Rappelez-vous ceci.
Éclaircir la perspective
Pour créer un objet 3D, j'avais besoin d'un élément (appelons-le une « scène ») avec une perspective. La perspective est la profondeur de la scène, et elle dépend de la taille des objets qu'elle contient.
.scene { perspective: 800px; }
Si la perspective est trop petite, les objets peuvent être déformés. S'il est trop grand, l'effet 3D sera réduit à néant.
Voir le stylo jqgMvL par Anna Selezniova (@askd) sur CodePen.
De plus, il n'y a qu'un seul angle de vue pour tous les objets de la scène. Et l'effet 3D dépend de la position du point de vue.
Voir le Pen oxKzKv par Anna Selezniova (@askd) sur CodePen.
Alors, comment calcule-t-on la perspective ? J'ai découvert que cela dépend de l'axe de rotation. Pour l'axe des x, la valeur de hauteur multipliée par 4 conviendrait. Pour l'axe des ordonnées, ce serait la valeur de la largeur multipliée par 4. Voici ma formule magique :
const perspective = dimension * 4;
Considéré de toutes parts
Après avoir déterminé la perspective, j'ai commencé à créer un objet 3D. J'ai choisi un cube parce qu'il est simple et prévisible. Un élément de cube est créé sous la forme d'un div régulier, relativement positionné, avec la largeur et la hauteur définies (par exemple, 200px
). Il se transforme en un objet 3D via la propriété transform-style
avec une valeur de preserve-3d
. Il indique au navigateur de rendre tous les éléments imbriqués selon les règles du monde 3D.
Dans mon cas, le cube a six divs (ou "côtés"), positionnés de manière absolue. Les noms de classe correspondent aux positions initiales des côtés ( back
, left
, right
, top
, bottom
, front
). Voici le balisage :
<div class="scene"> <div class="cube"> <div class="side back"></div> <div class="side left"></div> <div class="side right"></div> <div class="side top"></div> <div class="side bottom"></div> <div class="side front"></div> </div> </div>
Par défaut, tous les côtés seront sur un même plan. J'ai donc dû les réorganiser. Voici à quoi cela ressemble :
Voir le stylo mPNwPx par Anna Selezniova (@askd) sur CodePen.
Et voici le CSS résultant :
.cube { position:relative; width: 200px; height: 200px; transform-style: preserve-3d; } .side { position: absolute; width: 200px; height: 200px; } .back { transform: translateZ(-100px); } .left { transform: translateX(-100px) rotateY(90deg); } .right { transform: translateX(100px) rotateY(90deg); } .top { transform: translateY(-100px) rotateX(90deg); } .bottom { transform: translateY(100px) rotateX(90deg); } .front { transform: translateZ(100px); }
Pour faire pivoter le cube, j'ai défini la propriété transform
de l'élément cube sur un angle de rotation arbitraire le long de l'axe des x :
.cube { transform: rotateX(42deg); }
Surmonter les lacunes
Selon la mission, je devais faire pivoter le cube uniquement le long de l'axe des x, donc je n'avais pas besoin du côté gauche ou droit. J'ai ajouté des légendes pour les aligner sur les positions initiales des côtés restants.
J'ai commencé à faire pivoter le cube et j'ai constaté que les légendes en bas et à l'arrière étaient affichées à l'envers :
Voir le stylo GZVvMR par Anna Selezniova (@askd) sur CodePen.
Pour résoudre ce problème, j'ai fait pivoter chacun de ces côtés le long de l'axe des x de 180 degrés :
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
Aller au-delà de l'écran
J'ai commencé à remplir les côtés avec du contenu réel et j'ai immédiatement rencontré un autre problème. J'avais besoin d'afficher des lignes pointillées de 1 pixel, mais elles étaient floues et semblaient mauvaises.

Voir le Pen VjeBPg par Anna Selezniova (@askd) sur CodePen.
J'ai vite compris quel était le problème. Vous souvenez-vous de cette publicité télévisée 3D dans laquelle l'image s'étend au-delà de l'écran ? C'était quelque chose comme ça avec mon cube.
Si vous pouviez regarder le cube du côté gauche ou droit, vous verriez que son centre était sur le plan de l'écran (zéro sur l'axe z) et que la face avant était au-delà de l'écran. Par conséquent, il a augmenté visuellement et flou.
Voir le stylo WwVEMR par Anna Selezniova (@askd) sur CodePen.
Pour résoudre ce problème, j'ai déplacé le cube le long de l'axe z pour aligner la face avant sur le plan de l'écran :
.cube { transform:translateZ(-100px); }
Voici le cube maintenant, presque prêt :
Voir le Pen Xdvery par Anna Selezniova (@askd) sur CodePen.
Utiliser les nombres magiques
Je suppose que vous avez remarqué que j'utilise le nombre magique 100
pour déplacer les côtés le long de l'axe. La valeur 100
correspond exactement à la moitié de la hauteur de mon cube de test. Pourquoi la moitié de la hauteur ? Parce que ce serait le rayon d'un cercle inscrit dans un côté du cube (qui est un carré, apparemment).
const offset = dimension / 2;
Si j'avais besoin de faire tourner un prisme triangulaire, le cercle serait inscrit dans un triangle. Dans ce cas, la formule du décalage serait la suivante :
const offset = dimension / (2 * Math.sqrt(3));
Souffler le cube
Pour considérer la tâche comme terminée, j'ai dû tester le résultat dans différents navigateurs.
L'image que j'ai vue dans Internet Explorer m'a plongé dans la dépression. Pour avoir une idée de ce dont je parle, regardez la démo ci-dessous dans votre navigateur préféré. J'ai modifié une propriété qui a entraîné l'affichage incorrect du cube dans Internet Explorer. Cependant, ne regardez pas le code source avant d'avoir lu le paragraphe sous la démo ci-dessous.
Voir le Pen XKWMwV par Anna Selezniova (@askd) sur CodePen.
Le fait est qu'Internet Explorer ne prend pas en charge la propriété transform-style
avec une valeur de preserve-3d
. J'ai appris cela en regardant ma ressource fidèle Puis-je utiliser (voir note 1). Dans la démo ci-dessus, j'ai remplacé preserve-3d
par flat
. Le saviez-vous déjà ? Hé, je t'avais dit de ne pas jeter un coup d'œil !
J'étais bouleversé, mais je n'allais pas abandonner. Un problème est une occasion d'apprendre quelque chose de nouveau. D'ailleurs, j'avais accepté le défi.
À la recherche du point d'appui
Je cherchais un moyen de créer un objet 3D sans utiliser transform-style: preserve-3d
, et j'ai finalement découvert une propriété utile : transform-origin
. Il détermine le point central de la transformation d'un élément. J'ai créé une démo interactive ci-dessous, qui vous aidera à comprendre comment cela fonctionne :
Voir le stylo rLNmBp par Anna Selezniova (@askd) sur CodePen.
La rotation 3D de l'élément dans la démo est très similaire à la face avant d'un cube, n'est-ce pas ? C'est ce que j'ai utilisé.
(Au fait, avez-vous essayé de cocher la case backface-visibility: hidden
during the 3D rotation ? Cette propriété est utilisée pour masquer l'arrière de l'élément lors de la transformation 3D.)
Tout recommencer
J'ai commencé à refaire le cube. Je n'avais pas à interagir avec la scène dans son ensemble, j'ai donc supprimé la propriété de perspective
de l'élément de scene
et l'ai ajoutée à chaque transformation 3D, de sorte que maintenant chaque élément se transforme indépendamment. Aussi, j'ai défini de nouvelles propriétés pour chaque côté : transform-origin
avec une valeur égale à la position du centre du cube, et backface-visibility: hidden
. Voici comment les styles ont changé :
.scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }
Je devais mettre les côtés aux bons endroits. En raison de la propriété transform-origin
, je n'ai pas eu besoin de les déplacer, seulement de les faire pivoter autour des axes. C'est comme de la magie ! Voyons à quoi ça ressemble:
Voir le stylo zBYwEm par Anna Selezniova (@askd) sur CodePen.
Voici le CSS pour le placement des côtés :
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
Et ici, vous pouvez voir le nouveau cube en action :
Voir le stylo wWvdXd par Anna Selezniova (@askd) sur CodePen.
Redonner à César ce qui appartient à César
Le deuxième cube ressemble et tourne de la même manière que le premier. Mais dans ce cas, vous devez transformer chaque côté individuellement. Ce n'est peut-être pas très facile, surtout si vous souhaitez contrôler l'angle de rotation intermédiaire.
De plus, si vous ouvrez la démo dans Chrome, vous verrez que les côtés clignotent pendant la rotation, ce qui est très frustrant.
Au final, j'ai appliqué les deux approches en utilisant le simple test de transform-style: preserve-3d
. Le premier cube est celui par défaut. Le deuxième cube est destiné à Internet Explorer et aux navigateurs qui ne prennent pas en charge preserve-3d
.
Utiliser le pouvoir des mathématiques
Enfin, j'ai dû implémenter un effet de parallaxe. Habituellement, cet effet répond à l'action de l'utilisateur, qu'il s'agisse de la position du curseur de la souris ou de la barre de défilement. Dans ce cas, l'effet dépend de l'angle de rotation.
Voir le Pen QENyqm par Anna Selezniova (@askd) sur CodePen.
Alors, quelles données ai-je ? Tout d'abord, j'avais les points de début et de fin de la position de la légende ou, pour le dire simplement, son offset
de haut en bas par rapport au centre d'un côté. Deuxièmement, j'avais l' angle
de rotation du cube.
J'ai passé des heures à essayer de développer une formule. Puis, ça m'est venu à l'esprit. Voici ce qui m'est venu à l'esprit :

A l'aide des sinus et des cosinus, j'ai facilement calculé le décalage de chaque légende en fonction de l'angle. Voici les formules que j'ai trouvées :
const front_offset = offset * sin(angle) * -1; const bottom_offset = offset * cos(angle); const back_offset = offset * sin(angle); const top_offset = offset * cos(angle) * -1;
Résumé
La mission est maintenant terminée, et je peux apprécier le résultat et le partager avec vous. Voyez par vous-même comment cela fonctionne. Utilisez les touches de défilement ou fléchées pour faire pivoter le bloc promo. Essayez également de tirer le triangle noir à droite de haut en bas pour contrôler manuellement l'angle de rotation (malheureusement, cette fonctionnalité ne fonctionne pas dans Internet Explorer). Ça a l'air plutôt bien, n'est-ce pas? Et les performances sont plutôt élevées (environ 60 images par seconde).
Je suis très heureux d'avoir participé au développement de ce site. J'ai acquis une expérience utile en travaillant avec CSS 3D et j'ai découvert de nombreuses propriétés intéressantes. Plus important encore, j'ai appris qu'il ne faut jamais abandonner ; vous trouverez très probablement un moyen d'accomplir la tâche.
J'espère que vous avez apprécié mon histoire et que vous êtes maintenant prêt à relever de nouveaux défis.