Pular para o conteúdo principal

Conclusão ALL DAY LONG: animação 3D com HTML, CSS e JS

· Leitura de 4 minutos

Uma breve conclusão.


ALL DAY LONG (série de 7 partes)
  1. Introdução
  2. Conceito
  3. Imagens
  4. HTML
  5. CSS
  6. JS
  7. Conclusão

Conclusão

Esta implementação demonstra que animações 3D podem ser alcançadas com elementos HTML e estilos CSS, sem usar de canvas ou WebGL.

Ideias principais por trás da animação:

  • Decompor a imagem em pequenos fragmentos
  • Posicionar os fragmentos ao longo de um caminho em forma de estádio
  • Animar os fragmentos ao longo desse caminho
  • Usar pseudo-elementos para representar a face oposta do fragmento

Confira uma versão interativa no CodePen.

Resultado



HTML

index.html
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <link rel="stylesheet" href="./style.css" />    <title>All Day Long</title>  </head>  <body>    <div class="wrapper">      <div class="container">        <!-- fragmentos são adicionados pelo o script -->      </div>    </div>    <script src="./script.js"></script>  </body></html>

CSS

style.css
*,*::before,*::after {  margin: 0;  padding: 0;  box-sizing: border-box;}html {  background-color: #3b7cf3;}.wrapper {  min-height: 100dvh;  display: grid;  place-content: center;}.container {  transform-style: preserve-3d;  position: relative;  /* width, height, e transform são aplicados pelo o script */}.fragment-wrapper {  position: absolute;  transform-style: preserve-3d;  /* width, height, e animação são aplicados pelo o script */  /* a animação (e sua duração, atraso, e iteração) também é aplicada pelo o script */}.fragment {  position: absolute;  transform-style: preserve-3d;  background-image: url("/img/all-day-long/front.png");  background-size: 100% auto;  backface-visibility: hidden;  /* width, height, transform, e background-position-y são aplicados pelo o script */}.fragment::after {  content: "";  position: absolute;  top: 0;  left: 0;  transform: rotateY(180deg);  background-image: url("/img/all-day-long/back.png");  background-size: 100% auto;  backface-visibility: hidden;  /* essas propriedades são herdadas do `.fragment` */  width: inherit;  height: inherit;  background-position-y: inherit;}

JS

script.js
// ====================// constants// ====================const IMAGE_WIDTH = 200;const IMAGE_HEIGHT = 800;const FRAGMENT_WIDTH = IMAGE_WIDTH;const FRAGMENT_HEIGHT = 10;const PERIMETER = IMAGE_HEIGHT;const RADIUS = 36;const ROTATE_X = 56;const ROTATE_Y = 20;const ROTATE_Z = 310;const DURATION = 6_000;// ====================// elements// ====================const elements = {  container: document.querySelector(".container"),};// ====================// functions// ====================function render() {  const semiCircleLength = Math.PI * RADIUS;  const semiCircleProportion = semiCircleLength / PERIMETER;  const lineLength = PERIMETER / 2 - semiCircleLength;  const lineProportion = lineLength / PERIMETER;  const fragmentCount = Math.floor(PERIMETER / FRAGMENT_HEIGHT);  const delayStep = DURATION / fragmentCount;  const keyframes = [    {      transform: `translateY(0px) rotateX(0deg)`,      offset: 0,    },    {      transform: `translateY(0px) rotateX(180deg)`,      offset: semiCircleProportion,    },    {      transform: `translateY(${lineLength}px) rotateX(180deg)`,      offset: semiCircleProportion + lineProportion,    },    {      transform: `translateY(${lineLength}px) rotateX(360deg)`,      offset: semiCircleProportion + lineProportion + semiCircleProportion,    },    {      transform: `translateY(0px) rotateX(360deg)`,      offset: 1,    },  ];  elements.container.style.width = `${IMAGE_WIDTH}px`;  elements.container.style.height = `${lineLength}px`;  elements.container.style.transform =    `rotateX(${ROTATE_X}deg) ` +    `rotateY(${ROTATE_Y}deg) ` +    `rotateZ(${ROTATE_Z}deg)`;  const domFragment = document.createDocumentFragment();  for (let i = 0; i < fragmentCount; i++) {    const fragmentWrapper = document.createElement("div");    const fragment = document.createElement("div");    fragmentWrapper.classList.add("fragment-wrapper");    fragmentWrapper.style.width = `${FRAGMENT_WIDTH}px`;    fragmentWrapper.style.height = `${FRAGMENT_HEIGHT}px`;    fragment.classList.add("fragment");    fragment.style.width = `${FRAGMENT_WIDTH}px`;    fragment.style.height = `${FRAGMENT_HEIGHT}px`;    fragment.style.transform = `translateZ(${RADIUS}px)`;    fragment.style.backgroundPositionY = `${i * FRAGMENT_HEIGHT}px`;    fragmentWrapper.append(fragment);    domFragment.append(fragmentWrapper);    fragmentWrapper.animate(keyframes, {      duration: DURATION,      delay: -i * delayStep,      iterations: Infinity,    });  }  elements.container.append(domFragment);}// ====================// events// ====================window.addEventListener("load", render);

Imagens

front.png



back.png

Leitura recomendada

Funções e propriedades CSS usadas nesta implementação:

APIs JS usadas nesta implementação: