Desafio de front-end aceito: CSS 3D Cube
Publicados: 2022-03-10Você gosta de desafios? Você está disposto a assumir uma tarefa com a qual nunca se deparou antes e cumpri-la dentro de um prazo? E se, ao realizar a tarefa, você encontrar um problema que parece insolúvel? Quero compartilhar minha experiência de usar efeitos CSS 3D pela primeira vez em um projeto real e inspirá-lo a enfrentar desafios.
Foi um dia comum quando Eugene, gerente da CreativePeople, me escreveu. Ele me enviou um vídeo e explicou que estava desenvolvendo um conceito para um novo projeto e queria saber se era possível desenvolver algo parecido com o que estava no vídeo.
Leitura adicional no SmashingMag:
- Beercamp: uma experiência com CSS 3D
- Criando formas responsivas com clip-path e saindo da caixa
- Vamos brincar com CSS acelerado por hardware
Era um objeto 3D (um cubóide, para ser preciso) que girava em torno de um dos eixos. Eu já tinha alguma experiência em trabalhar com CSS 3D, e uma solução começou a se formar em minha mente. Eu pesquisei palavras-chave como “CSS 3D cube” para confirmar minhas ideias e respondi a Eugene que era possível.
A próxima pergunta de Eugene foi se eu aceitaria o projeto? Eu gosto de tarefas complicadas, então eu não poderia recusar. Na época, eu não percebi no que estava me metendo, mas estava além de determinada.
Afie seus machados
Vamos nos lembrar dos eixos — não dos machados de guerra, mas das linhas numéricas, os mesmos eixos do sistema de coordenadas cartesianas tridimensional que estudamos na escola. Como a Wikipedia nos diz:
O sistema de coordenadas cartesianas para um espaço tridimensional é um tripleto ordenado de linhas (eixos) que são perpendiculares aos pares, têm uma única unidade de comprimento para todos os três eixos e têm uma orientação para cada eixo.
A figura abaixo mostra como os eixos são orientados em um navegador da web.

O eixo x é horizontal, o eixo y é vertical e o eixo z parece sair da tela em sua direção. O valor zero do eixo z é o plano da tela. Lembre-se disso.
Esclarecendo a Perspectiva
Para criar um objeto 3D, eu precisava de um elemento (vamos chamá-lo de “cena”) com uma perspectiva. A perspectiva é a profundidade da cena e depende dos tamanhos dos objetos que ela contém.
.scene { perspective: 800px; }
Se a perspectiva for muito pequena, os objetos podem ficar distorcidos. Se for muito grande, o efeito 3D será reduzido a nada.
Veja a caneta jqgMvL de Anna Selezniova (@askd) no CodePen.
Além disso, há apenas um ângulo de visão para todos os objetos na cena. E o efeito 3D depende da posição do ponto de vista.
Veja a caneta oxKzKv de Anna Selezniova (@askd) no CodePen.
Então, como calculamos a perspectiva? Descobri que depende do eixo de rotação. Para o eixo x, o valor da altura multiplicado por 4 caberia. Para o eixo y, seria o valor da largura multiplicado por 4. Aqui está minha fórmula mágica:
const perspective = dimension * 4;
Considerado de todos os lados
Depois de determinar a perspectiva, comecei a criar um objeto 3D. Escolhi um cubo porque é direto e previsível. Um elemento de cubo é criado como um div regular, relativamente posicionado, com largura e altura definidas (digamos, 200px
). Ele se transforma em um objeto 3D através da propriedade transform-style
com um valor de preserve-3d
. Ele diz ao navegador para renderizar todos os elementos aninhados de acordo com as regras do mundo 3D.
No meu caso, o cubo tem seis divs (ou “lados”), absolutamente posicionados. Os nomes das classes correspondem às posições iniciais dos lados ( back
, left
, right
, top
, bottom
, front
). Aqui está a marcação:
<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>
Por padrão, todos os lados estarão em um plano. Então, eu precisava reorganizá-los. Aqui está como isso parece:
Veja a caneta mPNwPx de Anna Selezniova (@askd) no CodePen.
E aqui está o CSS resultante:
.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); }
Para girar o cubo, defino a propriedade transform
no elemento do cubo para um ângulo de rotação arbitrário ao longo do eixo x:
.cube { transform: rotateX(42deg); }
Superando as deficiências
De acordo com a tarefa, eu deveria girar o cubo apenas ao longo do eixo x, então não precisava do lado esquerdo ou direito. Adicionei legendas para alinhar com as posições iniciais dos lados restantes.
Comecei a girar o cubo e descobri que as legendas na parte inferior e traseira eram exibidas de cabeça para baixo:
Veja a caneta GZVvMR de Anna Selezniova (@askd) no CodePen.
Para resolver esse problema, girei cada um desses lados ao longo do eixo x em 180 graus:
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
Indo além da tela
Comecei a preencher os lados com conteúdo real e imediatamente encontrei outro problema. Eu precisava exibir linhas pontilhadas de 1 pixel, mas elas estavam embaçadas e pareciam ruins.

Veja a caneta VjeBPg de Anna Selezniova (@askd) no CodePen.
Logo percebi qual era o problema. Você se lembra daquele anúncio de TV 3D em que a imagem se estende além da tela? Foi algo assim com o meu cubo.
Se você pudesse olhar para o cubo do lado esquerdo ou direito, veria que seu centro estava no plano da tela (zero no eixo z) e que o lado frontal estava além da tela. Portanto, aumentou visualmente e desfocado.
Veja a caneta WwVEMR de Anna Selezniova (@askd) no CodePen.
Para resolver esse problema, desloquei o cubo ao longo do eixo z para alinhar a parte frontal ao plano da tela:
.cube { transform:translateZ(-100px); }
Aqui está o cubo agora, quase pronto:
Veja o Pen Xdvery de Anna Selezniova (@askd) no CodePen.
Usando os números mágicos
Acho que você notou que estou usando o número mágico 100
para deslocar os lados ao longo do eixo. O valor 100
é exatamente metade da altura do meu cubo de teste. Por que metade da altura? Porque esse seria o raio de um círculo inscrito em um lado do cubo (que é um quadrado, aparentemente).
const offset = dimension / 2;
Se eu precisasse girar um prisma triangular, o círculo estaria inscrito em um triângulo. Nesse caso, a fórmula para o deslocamento seria a seguinte:
const offset = dimension / (2 * Math.sqrt(3));
Soprando o cubo
Para considerar a tarefa concluída, tive que testar o resultado em diferentes navegadores.
A imagem que vi no Internet Explorer me mergulhou na depressão. Para ter uma ideia do que estou falando, veja a demonstração abaixo em seu navegador favorito. Alterei uma propriedade que resultou na exibição incorreta do cubo no Internet Explorer. No entanto, não espie o código-fonte antes de ler o parágrafo da demonstração abaixo.
Veja a caneta XKWMwV de Anna Selezniova (@askd) no CodePen.
O fato é que o Internet Explorer não suporta a propriedade transform-style
com valor preserve-3d
. Eu aprendi sobre isso olhando para o meu recurso confiável Posso usar (veja a nota 1). Na demonstração acima, substituí preserve-3d
por flat
. Você já sabia disso? Ei, eu disse para você não espiar!
Fiquei chateado, mas não ia desistir. Um problema é uma oportunidade de aprender algo novo. Além disso, eu tinha aceitado o desafio.
Procurando o fulcro
Eu estava procurando uma maneira de criar um objeto 3D sem usar transform-style: preserve-3d
, e acabei descobrindo uma propriedade útil: transform-origin
. Determina o ponto central da transformação de um elemento. Eu criei uma demonstração interativa abaixo, que ajudará você a entender como funciona:
Veja a caneta rLNmBp de Anna Selezniova (@askd) no CodePen.
A rotação 3D do elemento na demonstração é muito semelhante à parte frontal de um cubo, não é? Isso é o que eu usei.
(A propósito, você tentou marcar a caixa de seleção backface-visibility: hidden
durante a rotação 3D? Esta propriedade é usada para ocultar a parte de trás do elemento durante a transformação 3D.)
Começando tudo de novo
Comecei a refazer o cubo. Eu não precisava interagir com a cena como um todo, então removi a propriedade perspective
do elemento de scene
e a adicionei a cada transformação 3D, de modo que agora cada elemento se transforma de forma independente. Além disso, defini novas propriedades para cada lado: transform-origin
com um valor igual à posição do centro do cubo e backface-visibility: hidden
. Veja como os estilos mudaram:
.scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }
Eu tive que colocar os lados nos lugares certos. Por causa da propriedade transform-origin
, não precisei deslocá-los, apenas girá-los em torno dos eixos. É como mágica! Vamos ver como fica:
Veja a caneta zBYwEm de Anna Selezniova (@askd) no CodePen.
Aqui está o CSS para o posicionamento dos lados:
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
E aqui você pode ver o novo cubo em ação:
Veja a caneta wWvdXd por Anna Selezniova (@askd) no CodePen.
Devolvendo a César o que é de César
O segundo cubo parece e gira da mesma forma que o primeiro. Mas neste caso, você precisa transformar cada lado individualmente. Isso pode não ser muito fácil, especialmente se você quiser controlar o ângulo de rotação intermediário.
Além disso, se você abrir a demonstração no Chrome, verá que os lados piscam durante a rotação - muito frustrante.
No final, apliquei as duas abordagens usando o teste simples de transform-style: preserve-3d
. O primeiro cubo é o padrão. O segundo cubo é para o Internet Explorer e navegadores que não suportam preserve-3d
.
Usando o poder da matemática
Finalmente, tive que implementar um efeito de paralaxe. Normalmente, esse efeito responde à ação do usuário, seja a posição do cursor do mouse ou a barra de rolagem. Neste caso, o efeito depende do ângulo de rotação.
Veja a caneta QENyqm de Anna Selezniova (@askd) no CodePen.
Então, quais dados eu tenho? Primeiro, eu tinha os pontos inicial e final da posição da legenda ou, para simplificar, seu offset
para cima e para baixo a partir do centro de um lado. Em segundo lugar, eu tinha o angle
de rotação do cubo.
Passei horas tentando desenvolver uma fórmula. Então, me dei conta. Aqui está o que me veio à mente:

Com a ajuda de senos e cossenos, calculei facilmente o deslocamento de cada legenda de acordo com o ângulo. Aqui estão as fórmulas que eu criei:
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;
Resumindo
A tarefa está concluída e posso aproveitar o resultado e compartilhá-lo com você. Veja você mesmo como funciona. Use as teclas de rolagem ou seta para girar o bloco promocional. Além disso, tente puxar o triângulo preto à direita para cima e para baixo para controlar o ângulo de rotação manualmente (infelizmente, esse recurso não funciona no Internet Explorer). Parece muito bom, não é? E o desempenho é bastante alto (cerca de 60 quadros por segundo).
Estou muito feliz por ter participado no desenvolvimento deste site. Adquiri uma experiência útil ao trabalhar com CSS 3D e descobri muitas propriedades interessantes. Mais importante, aprendi que nunca se deve desistir; muito provavelmente você encontrará uma maneira de realizar a tarefa.
Espero que tenham gostado da minha história e que agora estejam prontos para novos desafios.