Provocare front-end acceptată: CSS 3D Cube
Publicat: 2022-03-10Îți plac provocările? Sunteți dispus să vă ocupați de o sarcină pe care nu ați mai întâlnit-o până acum și să o faceți într-un termen limită? Ce se întâmplă dacă, în îndeplinirea sarcinii, întâmpinați o problemă care pare de nerezolvat? Vreau să împărtășesc experiența mea de utilizare a efectelor 3D CSS pentru prima dată într-un proiect real și să vă inspir să acceptați provocările.
Era o zi obișnuită când Eugene, un manager la CreativePeople, mi-a scris. Mi-a trimis un videoclip și mi-a explicat că elaborează un concept pentru un nou proiect și se întreba dacă este posibil pentru mine să dezvolt ceva ca ceea ce era în videoclip.
Citiți suplimentare despre SmashingMag:
- Beercamp: un experiment cu CSS 3D
- Crearea de forme receptive cu traseu de clipare și ruperea din cutie
- Să ne jucăm cu CSS accelerat de hardware
Era un obiect 3D (un cuboid, mai exact) care se rotea în jurul uneia dintre axe. Aveam deja ceva experiență în lucrul cu CSS 3D și în mintea mea a început să se formeze o soluție. Am căutat pe Google cuvinte cheie precum „CSS 3D cube” pentru a-mi confirma ideile și i-am răspuns lui Eugene că este posibil.
Următoarea întrebare a lui Eugene a fost dacă mă voi ocupa de proiect? Îmi plac sarcinile complicate, așa că nu am putut refuza. La acea vreme, nu mi-am dat seama în ce mă bag, dar eram peste hotare.
Ascuțiți-vă topoarele
Să ne reamintim despre axe — nu axe de război, ci linii numerice, aceleași axe ca în sistemul de coordonate carteziene tridimensional pe care l-am studiat la școală. După cum ne spune Wikipedia:
Sistemul de coordonate carteziene pentru un spațiu tridimensional este un triplet ordonat de linii (axe) care sunt perpendiculare în perechi, au o singură unitate de lungime pentru toate cele trei axe și au o orientare pentru fiecare axă.
Imaginea de mai jos arată cum sunt orientate axele într-un browser web.

Axa x este orizontală, axa y este verticală, iar axa z pare să iasă de pe ecran spre tine. Valoarea zero a axei z este planul ecranului. Tine minte asta.
Limpezirea Perspectivei
Pentru a crea un obiect 3D, aveam nevoie de un element (să-i spunem „scenă”) cu o perspectivă. Perspectiva este profunzimea scenei și depinde de dimensiunile obiectelor pe care le conține.
.scene { perspective: 800px; }
Dacă perspectiva este prea mică, obiectele ar putea fi distorsionate. Dacă este prea mare, efectul 3D va fi redus la nimic.
Vedeți Pen jqgMvL de Anna Selezniova (@askd) pe CodePen.
În plus, există un singur unghi de vedere pentru toate obiectele din scenă. Iar efectul 3D depinde de poziția punctului de vedere.
Vedeți Pen oxKzKv de Anna Selezniova (@askd) pe CodePen.
Deci, cum calculăm perspectiva? Am descoperit că depinde de axa de rotație. Pentru axa x, valoarea înălțimii înmulțită cu 4 s-ar potrivi. Pentru axa y, ar fi valoarea lățimii înmulțită cu 4. Iată formula mea magică:
const perspective = dimension * 4;
Considerat din toate părțile
După ce am determinat perspectiva, am început să creez un obiect 3D. Am ales un cub pentru că este simplu și previzibil. Un element cub este creat ca un div obișnuit, poziționat relativ, cu lățimea și înălțimea definite (să zicem, 200px
). Se transformă într-un obiect 3D prin proprietatea transform-style
cu o valoare de preserve-3d
. Spune browserului să redeze toate elementele imbricate conform regulilor lumii 3D.
În cazul meu, cubul are șase div-uri (sau „laturi”), poziționate absolut. Numele claselor corespund pozițiilor inițiale ale laturilor ( back
, left
, right
, top
, bottom
, front
). Iată marcajul:
<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>
În mod implicit, toate laturile vor fi pe un singur plan. Deci, trebuia să le rearanjez. Iată cum arată:
Vedeți Pen mPNwPx de Anna Selezniova (@askd) pe CodePen.
Și iată CSS-ul rezultat:
.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); }
Pentru a roti cubul, am stabilit proprietatea de transform
a elementului cub la un unghi de rotație arbitrar de-a lungul axei x:
.cube { transform: rotateX(42deg); }
Depășirea Deficiențelor
Conform misiunii, trebuia să rotesc cubul numai de-a lungul axei x, așa că nu aveam nevoie de partea stângă sau dreaptă. Am adăugat legende pentru a se alinia cu pozițiile inițiale ale părților rămase.
Am început să rotesc cubul și am constatat că legendele de pe partea de jos și din spate erau afișate cu susul în jos:
Vedeți Pen GZVvMR de Anna Selezniova (@askd) pe CodePen.
Pentru a rezolva această problemă, am rotit fiecare dintre aceste laturi de-a lungul axei x cu 180 de grade:
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
Trecând dincolo de ecran
Am început să umplu părțile laterale cu conținut real și imediat am întâmpinat o altă problemă. Trebuia să afișez linii punctate de 1 pixel, dar erau neclare și arătau rău.

Vedeți Pen VjeBPg de Anna Selezniova (@askd) pe CodePen.
Curând mi-am dat seama care era problema. Vă amintiți acea reclamă TV 3D în care imaginea se extinde dincolo de ecran? A fost așa ceva cu cubul meu.
Dacă ați putea privi cubul din partea stângă sau din dreapta, ați vedea că centrul său se află în planul ecranului (zero pe axa z) și că partea din față era dincolo de ecran. Prin urmare, a crescut vizual și s-a încețoșat.
Vedeți Pen WwVEMR de Anna Selezniova (@askd) pe CodePen.
Pentru a rezolva această problemă, am deplasat cubul de-a lungul axei z pentru a alinia partea frontală la planul ecranului:
.cube { transform:translateZ(-100px); }
Iată acum cubul, aproape gata:
Vedeți Pen Xdvery de Anna Selezniova (@askd) pe CodePen.
Folosind numerele magice
Cred că ați observat că folosesc numărul magic 100
pentru a deplasa părțile laterale de-a lungul axei. Valoarea 100
este exact jumătate din înălțimea cubului meu de testare. De ce jumătate din înălțime? Pentru că aceasta ar fi raza unui cerc înscris într-o latură a cubului (care este un pătrat, aparent).
const offset = dimension / 2;
Dacă ar trebui să rotesc o prismă triunghiulară, cercul ar fi înscris într-un triunghi. În acest caz, formula pentru compensare ar fi următoarea:
const offset = dimension / (2 * Math.sqrt(3));
Îndepărtând Cubul
Pentru a considera sarcina finalizată, a trebuit să testez rezultatul în diferite browsere.
Poza pe care am văzut-o în Internet Explorer m-a cufundat în depresie. Pentru a vă face o idee despre ce vorbesc, uitați-vă la demonstrația de mai jos în browserul dvs. preferat. Am schimbat o proprietate care a dus la afișarea incorectă a cubului în Internet Explorer. Cu toate acestea, nu aruncați o privire la codul sursă până nu citiți paragraful de sub demonstrația de mai jos.
Vedeți Pen XKWMwV de Anna Selezniova (@askd) pe CodePen.
Faptul este că Internet Explorer nu acceptă proprietatea transform-style
cu o valoare preserve-3d
. Am aflat despre asta uitându-mă la resursa mea de încredere Can I Use (vezi nota 1). În demonstrația de mai sus, am înlocuit preserve-3d
cu flat
. Știai deja asta? Hei, ți-am spus să nu te uiți!
Eram supărat, dar nu aveam de gând să renunț. O problemă este o oportunitate de a învăța ceva nou. În plus, acceptasem provocarea.
În căutarea punctului de sprijin
Căutam o modalitate de a crea un obiect 3D fără a folosi transform-style: preserve-3d
și, în cele din urmă, am descoperit o proprietate utilă: transform-origin
. Determină punctul central al transformării unui element. Am creat mai jos o demonstrație interactivă, care vă va ajuta să înțelegeți cum funcționează:
Vedeți Pen rLNmBp de Anna Selezniova (@askd) pe CodePen.
Rotirea 3D a elementului din demonstrație este foarte asemănătoare cu partea frontală a unui cub, nu-i așa? Asta am folosit.
(Apropo, ați încercat să bifați caseta de selectare backface-visibility: hidden
în timpul rotației 3D? Această proprietate este folosită pentru a ascunde partea din spate a elementului în timpul transformării 3D.)
Începând de la capăt
Am început să refac cubul. Nu a trebuit să interacționez cu scena ca întreg, așa că am eliminat proprietatea de perspective
a elementului scene
și am adăugat-o la fiecare transformare 3D, astfel încât acum fiecare element să se transforme independent. De asemenea, am setat noi proprietăți pentru fiecare parte: transform-origin
cu o valoare egală cu poziția centrului cubului și backface-visibility: hidden
. Iată cum s-au schimbat stilurile:
.scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }
A trebuit să pun părțile laterale în locurile potrivite. Din cauza proprietății transform-origin
, nu a trebuit să le deplasez, ci doar să le rotesc în jurul axei. E ca o magie! Să vedem cum arată:
Vedeți Pen zBYwEm de Anna Selezniova (@askd) pe CodePen.
Iată CSS-ul pentru plasarea laturilor:
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
Și aici puteți vedea noul cub în acțiune:
Vedeți Pen wWvdXd de Anna Selezniova (@askd) pe CodePen.
A da înapoi Cezarului Ce este al Cezarului
Al doilea cub arată și se învârte la fel ca primul. Dar, în acest caz, trebuie să transformați fiecare parte individual. Acest lucru ar putea să nu fie foarte ușor, mai ales dacă doriți să controlați unghiul intermediar de rotație.
În plus, dacă deschideți demonstrația în Chrome, veți vedea că părțile laterale clipesc în timpul rotației - foarte frustrant.
În cele din urmă, am aplicat ambele abordări folosind testul simplu al transform-style: preserve-3d
. Primul cub este cel implicit. Al doilea cub este pentru Internet Explorer și browsere care nu acceptă preserve-3d
.
Folosind puterea matematicii
În cele din urmă, a trebuit să implementez un efect de paralaxă. De obicei, acest efect răspunde la acțiunea utilizatorului, indiferent dacă este poziția cursorului mouse-ului sau bara de defilare. În acest caz, efectul depinde de unghiul de rotație.
Vedeți Pen QENyqm de Anna Selezniova (@askd) pe CodePen.
Deci, ce date am? În primul rând, aveam punctele de început și de sfârșit ale poziției legendei sau, pentru a spune simplu, offset
acesteia în sus și în jos față de centrul unei laturi. În al doilea rând, am avut angle
de rotație al cubului.
Am petrecut ore întregi încercând să dezvolt o formulă. Apoi, mi-a dat seama. Iată ce mi-a venit în minte:

Cu ajutorul sinusurilor și cosinusurilor, am calculat cu ușurință offset-ul fiecărei legende în funcție de unghi. Iată formulele cu care am venit:
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;
Rezumând
Sarcina este acum finalizată și mă pot bucura de rezultat și vă pot împărtăși. Vedeți singur cum funcționează. Utilizați tastele de derulare sau săgeți pentru a roti blocul promoțional. De asemenea, încercați să trageți triunghiul negru din dreapta în sus și în jos pentru a controla manual unghiul de rotație (din păcate, această caracteristică nu funcționează în Internet Explorer). Arată destul de bine, nu-i așa? Și performanța este destul de ridicată (aproximativ 60 de cadre pe secundă).
Sunt foarte bucuros că am participat la dezvoltarea acestui site web. Am câștigat experiență utilă în lucrul cu CSS 3D și am descoperit multe proprietăți interesante. Mai important, am învățat că nu trebuie să renunți niciodată; cel mai probabil vei găsi o modalitate de a îndeplini sarcina.
Sper că ți-a plăcut povestea mea și că acum ești gata să faci față noilor provocări.