Pure CSS Shapes & solids #loop #shape #grid

Cube field wave in pure CSS

Sixteen cubes seen isometrically, rising and falling in a wave that travels along the diagonals. Each cube is one element: its top plus two folded pseudo-element sides.

  • 62 lines of CSS
  • 20 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. Isometric view: turn a flat grid rotateZ(-45deg), then tip it back with rotateX. "Up" is now the grid's own Z axis, so translateZ lifts a cube straight off the floor.
  2. From this angle only the top and two sides of a cube can ever be seen. So each cube is one element: the element is the top, and ::before / ::after are the sides, hinged on its edges and folded down.
  3. Every cube runs the same up-and-down animation. --d is row + column, so cubes on the same diagonal share a delay and the wave travels corner to corner.
  4. The cube never leaves its grid cell and only transform animates, so 16 cubes stay cheap.
  5. Every length is a multiple of one base unit, --u, tied to the canvas: a cube is 36 units, and the wave lifts it 44. The field is the same share of a gallery card, the editor and a recording canvas.

Key techniques

  • rotateX + rotateZ isometric floor
  • only the two visible sides, as ::before / ::after
  • translateZ along the floor normal
  • delay = (row + column) × step

Copy-paste code

HTML 20 lines

<div class="scene">
  <div class="field">
    <i style="--d:0"></i>
    <i style="--d:1"></i>
    <i style="--d:2"></i>
    <i style="--d:3"></i>
    <i style="--d:1"></i>
    <i style="--d:2"></i>
    <i style="--d:3"></i>
    <i style="--d:4"></i>
    <i style="--d:2"></i>
    <i style="--d:3"></i>
    <i style="--d:4"></i>
    <i style="--d:5"></i>
    <i style="--d:3"></i>
    <i style="--d:4"></i>
    <i style="--d:5"></i>
    <i style="--d:6"></i>
  </div>
</div>

CSS 62 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the field is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.34vmin;
  perspective: calc(800 * var(--u));
}

.field {
  display: grid;
  grid-template-columns: repeat(4, calc(36 * var(--u)));
  gap: calc(10 * var(--u));
  transform-style: preserve-3d;
  /* isometric floor: turn 45deg, then tip back. The cube sides fold down below the floor and the
     near corner is magnified, so what the field paints hangs below its layout box even with the
     wave lifting the cubes: the floor is raised that much and the whole field stays centred. */
  transform: translateY(calc(-8 * var(--u))) rotateX(58deg) rotateZ(-45deg);
}

/* the element itself is the top of the cube */
.field i {
  --hue: calc(253 - var(--d) * 13); /* violet corner to teal corner */
  position: relative;
  width: calc(36 * var(--u));
  height: calc(36 * var(--u));
  background: hsl(var(--hue) 90% 80%);
  transform-style: preserve-3d;
  animation: wave 2.6s ease-in-out infinite;
  animation-delay: calc(var(--d) * -0.28s);
}

.field i::before,
.field i::after {
  content: '';
  position: absolute;
}

/* side hinged on the bottom edge, folded down */
.field i::before {
  left: 0;
  top: 100%;
  width: 100%;
  height: calc(36 * var(--u));
  background: hsl(var(--hue) 85% 64%);
  transform-origin: top;
  transform: rotateX(-90deg);
}

/* side hinged on the left edge, folded down */
.field i::after {
  right: 100%;
  top: 0;
  width: calc(36 * var(--u));
  height: 100%;
  background: hsl(var(--hue) 55% 36%);
  transform-origin: right;
  transform: rotateY(-90deg);
}

@keyframes wave {
  0%, 100% { transform: translateZ(0); }
  50%      { transform: translateZ(calc(44 * var(--u))); }
}
Standalone snippet · plain CSS, no build step, no dependencies.