Há algo em mim que fica maravilhado com as belas interfaces 3D. E não importa se elas são funcionais, como os recursos do giroscópio menu, demonstradores de tecnologia como a incrível demonstração da tabela periódica da famoso ou são representações artísticas que ultrapassam os limites da tecnologia CSS atual, como as canetas do Ana Tudor ou Hakim El Hattab. Adoro todos eles e procuro uma desculpa para usar algo semelhante em meus aplicativos.
Há alguns meses, experimentei o React e fiquei surpreso com a rapidez com que me tornei produtivo com ele, permitindo que eu me concentrasse em escrever meu aplicativo. Rapidamente adaptei um tema bootstrap, orquestrei a interatividade e a interface do aplicativo ficou pronta em pouco tempo. No entanto, senti que algo estava faltando. Assim que a aparência básica pôde ser feita com tão pouco esforço, a vozinha dentro de mim se sentiu insatisfeita. Os objetivos foram mudados, eu queria animação.
“Se tudo parece estar sob controle, o senhor não está indo rápido o suficiente.”
– Mario Andretti
Aprendendo as cordas
Decidi criar um carrossel 3D com React, como um exercício autônomo de como a estrutura funciona com componentes pequenos, mas autossustentáveis. Minha primeira regra era que a animação não seria reiniciada quando eu adicionasse ou removesse elementos, alterasse o layout ou girasse o carrossel. Essa restrição rígida, que não existia nem mesmo nos carrosséis comerciais, fez a diferença subjacente no design.
No início, dei uma olhada no React ReactCSSTransitionGroup mas, de alguma forma, isso não parecia certo e os comentários nos fóruns sobre a adição e remoção de elementos eram assustadores. Por isso, comecei a trabalhar manualmente usando o Dave DeSandro tutorial sobre transformações 3D. Desenhar o carrossel com o layout sugerido não foi muito difícil, e consegui transformar as regras CSS do DeSandro em funções JavaScript sem muitos problemas.
Obtenção de efeito 3D
O carrossel é composto pelos quatro elementos:
1. Uma seção que contém as imagens e os controles, tem comprimento e largura estáticos, contém perspectiva e sua relativaao elemento pai.
// Static styling .react-3d-carousel { width: 400px; height: 300px; position: relative; perspective: 1000px; }
2. A div
contendo as figuras mostradas, seu position
é absolute
e tem transform-style: preserve-3d
propriedade. Esse elemento é convertido no eixo Z para que o carrossel fique na distância correta do visualizador.
// Static styling .react-3d-carousel .carousel { // image container width: 100%; height: 100%; position: absolute; transform-style: preserve-3d; } // Dynamic styling example transform:translateZ(-347px);
Para o layout do prisma, a distância é calculada como o apótema do polígono. Para o layout clássico do carrossel, experimentei algumas fórmulas até chegar a algo que parecesse bom.
function apothem(width, sides) { return Math.ceil(width / (2 * Math.tan(Math.PI / sides))); } function magic(width, sides) { return Math.round(width * Math.log(sides)) }
3. A tag Figure representa a única imagem mostrada. Ela é estilizada dinamicamente de acordo com o layout fornecido e o estado da animação. Como as definições de função são muito grandes, verifique a tag layout para o cálculo correspondente. A seguir, apresento o estilo estático com um exemplo do estilo de elemento gerado a partir das funções de layout.
// Static styling .react-3d-carousel .carousel figure { // single image display: block; position: absolute; width: 400px; height: 300px; } // Dynamic styling // Prism layout figure example styling transform: rotateY(1.047rad) translateX(0px) translateZ(347px); opacity: 1; // Classic layout figure example styling transform: rotateY(0rad) translateX(620.94px) translateZ(358.5px); opacity: 1;
4. Controles – Atualmente, os controles têm estilos estáticos usando dois chevrons brancos como plano de fundo. style.css . Abaixo está o snippet que representa os chevrons direcionais.
.react-3d-carousel .prev:before { content: url("chevron_left_white.png"); } .react-3d-carousel .next:before { content: url("chevron_right_white.png"); }
Adicionando layouts
Se o senhor quiser adicionar um layout adicional, como o usado no Carrossel 3D real ou a topologia em estrela do Carrossel 3D definitivo o senhor precisa implementar duas funções que calculam a distância do visualizador e a posição de cada figura.
// Receives width of the image and number of sides returns // distance from the viewer to the carousel function distance(width, sides) { // Receives width of the image, images and initial rotation // of the carousel, return array of all images with their // corresponding transformations & rotations function figures(width, images, initial) {
Isso permite que o carrossel seja extensível com layout adicional, mas o usuário terá que encontrar uma maneira de expressar o layout como uma função.
Estado de gerenciamento
Por outro lado, o gerenciamento do estado foi problemático desde o início. O carrossel tem um estado intermediário quando gira e quando adiciona ou remove lados, o que é agravado pela capacidade de alterar o layout em tempo real, o que muda completamente a aparência do carrossel. Minha primeira abordagem foi usar uma biblioteca ou mecanismo de animação para gerenciar o estado. Havia algumas bibliotecas interessantes que poderiam ter facilitado minha vida, mas como o carrossel era um componente, eu não gostava muito de forçar as pessoas a adicionar dependências que são magnitudes maiores do que a funcionalidade de que realmente precisam. Como o RxJS não parecia tão pesado no momento, fiz minha primeira iteração usando Paul Taylor porto de Robert Penner funções de facilitação e uso Ramda para tarefas funcionais de baixo nível.
O estado do carrossel é armazenado no depot que permite que o usuário passe novas propriedades e/ou gire o carrossel.
Remoção de dependências
O código funcionava, mas era uma bagunça, e o RxJS e o Ramda ainda pareciam muito pesados, portanto, na segunda iteração, extraí todas as funcionalidades de que precisava e refatorei o código. A capacidade de alterar o layout em tempo real me levou a um design muito funcional. Trabalhar diretamente com o requestAnimationFrame revelou alguns problemas muito interessantes, como por que meu código é chamado apenas uma vez.
Publicação e limpeza
Meu componente estava pronto, então juntei a demonstração que foi usada durante o desenvolvimento como um exemplo e a publiquei no Github. No início, havia muitos problemas que eu não havia previsto, a maioria deles apontada pelo Juho Vepsäläinen com seus conselhos úteis sobre como corrigi-los. Fiquei muito surpreso quando outro usuário não conseguiu instalar o carrossel. Mas acho que os bugs vêm com os usuários e, no final, consegui corrigi-los e tornar o carrossel utilizável.
Conclusão
O carrossel representa o exemplo em miniatura do fluxo de dados unidirecional e da capacidade de gerenciar o estado intermediário. É apenas um pequeno passo em direção à próxima geração de interfaces de usuário, em que a animação será a espinha dorsal da interação, em vez de um atrativo para o usuário. Com o aumento da velocidade de processamento do hardware, os navegadores estão prontos, o resto é com o software.
O senhor pode encontrar a fonte em github e veja o carrossel pronto em codepen. Experimente alterar o layout, as funções de flexibilização, o número de lados e, é claro, girar o carrossel.