Pure CSS Shapes & solids #loop #shape #grid

Breathing lattice in pure CSS

A 3×3×3 wireframe: three layers of bars and glowing nodes, and nine posts through them. The outer layers move apart while the posts stretch by the same amount, so no joint ever opens.

  • 95 lines of CSS
  • 66 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. A single flat bar vanishes whenever you see it edge-on, so every bar is two crossed planes: the element and its ::after turned rotateX(90deg) around the bar's own length.
  2. One bar rule, three directions: along X as it is, along Z with rotateY(90deg), standing up with rotateZ(90deg). --x, --y, --z (−1, 0 or 1) times the spacing place it.
  3. The lattice is three horizontal layers (6 bars and 9 nodes each) plus 9 posts running through all of them. A layer moves to translateY(y × 1.3 × step): the middle one has y = 0, so the same keyframes leave it in place.
  4. The posts wrapper does scaleY(1.3) on the same beat, so the posts stretch by exactly what the layers move and no joint ever opens.
  5. Every length is a multiple of one base unit, --u, tied to the canvas, so the lattice is the same share of a gallery card, the editor and a recording canvas. The spacing, --step, is 50 of those units, and the lattice is sized for its fullest breath.
  6. Nodes are round glows that turn back against the spin (rotateY(-360deg), same timing), so they always face you.

Key techniques

  • bars as two crossed planes
  • layers: translateY(y × distance)
  • posts: scaleY in step with the layers
  • billboard nodes

Copy-paste code

HTML 66 lines

<div class="scene">
  <div class="lattice">
    <div class="layer" style="--y:-1">
      <i style="--z:-1"></i>
      <i style="--z:0"></i>
      <i style="--z:1"></i>
      <i class="z" style="--x:-1"></i>
      <i class="z" style="--x:0"></i>
      <i class="z" style="--x:1"></i>
      <b style="--x:-1; --z:-1"></b>
      <b style="--x:0; --z:-1"></b>
      <b style="--x:1; --z:-1"></b>
      <b style="--x:-1; --z:0"></b>
      <b style="--x:0; --z:0"></b>
      <b style="--x:1; --z:0"></b>
      <b style="--x:-1; --z:1"></b>
      <b style="--x:0; --z:1"></b>
      <b style="--x:1; --z:1"></b>
    </div>
    <div class="layer mid" style="--y:0">
      <i style="--z:-1"></i>
      <i style="--z:0"></i>
      <i style="--z:1"></i>
      <i class="z" style="--x:-1"></i>
      <i class="z" style="--x:0"></i>
      <i class="z" style="--x:1"></i>
      <b style="--x:-1; --z:-1"></b>
      <b style="--x:0; --z:-1"></b>
      <b style="--x:1; --z:-1"></b>
      <b style="--x:-1; --z:0"></b>
      <b style="--x:0; --z:0"></b>
      <b style="--x:1; --z:0"></b>
      <b style="--x:-1; --z:1"></b>
      <b style="--x:0; --z:1"></b>
      <b style="--x:1; --z:1"></b>
    </div>
    <div class="layer" style="--y:1">
      <i style="--z:-1"></i>
      <i style="--z:0"></i>
      <i style="--z:1"></i>
      <i class="z" style="--x:-1"></i>
      <i class="z" style="--x:0"></i>
      <i class="z" style="--x:1"></i>
      <b style="--x:-1; --z:-1"></b>
      <b style="--x:0; --z:-1"></b>
      <b style="--x:1; --z:-1"></b>
      <b style="--x:-1; --z:0"></b>
      <b style="--x:0; --z:0"></b>
      <b style="--x:1; --z:0"></b>
      <b style="--x:-1; --z:1"></b>
      <b style="--x:0; --z:1"></b>
      <b style="--x:1; --z:1"></b>
    </div>
    <div class="posts">
      <i style="--x:-1; --z:-1"></i>
      <i style="--x:0; --z:-1"></i>
      <i style="--x:1; --z:-1"></i>
      <i style="--x:-1; --z:0"></i>
      <i style="--x:0; --z:0"></i>
      <i style="--x:1; --z:0"></i>
      <i style="--x:-1; --z:1"></i>
      <i style="--x:0; --z:1"></i>
      <i style="--x:1; --z:1"></i>
    </div>
  </div>
</div>

CSS 95 lines

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

.lattice {
  --step: calc(50 * var(--u)); /* the spacing between nodes */
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: spin 18s linear infinite;
}

.layer,
.posts {
  position: absolute;
  transform-style: preserve-3d;
}

.layer {
  transform: translateY(calc(var(--y) * var(--step)));
  animation: breathe 2.4s ease-in-out infinite alternate;
}

.posts {
  animation: stretch 2.4s ease-in-out infinite alternate;
}

/* a bar along X, two crossed planes */
.lattice i {
  position: absolute;
  top: calc(-1 * var(--u));
  left: calc(var(--step) * -1);
  width: calc(var(--step) * 2);
  height: calc(2 * var(--u));
  transform-style: preserve-3d;
  background: linear-gradient(90deg, #2ee6d6, rgb(139 108 255 / 0.85) 30% 70%, #2ee6d6);
  transform: translateZ(calc(var(--z) * var(--step)));
}

.lattice i::after {
  content: '';
  position: absolute;
  inset: 0;
  background: inherit;
  transform: rotateX(90deg);
}

/* the same bar along Z */
.lattice i.z {
  transform: translateX(calc(var(--x) * var(--step))) rotateY(90deg);
}

/* and standing up */
.posts i {
  transform: translateX(calc(var(--x) * var(--step))) translateZ(calc(var(--z) * var(--step))) rotateZ(90deg);
}

.lattice b {
  --glow: 46 230 214;
  position: absolute;
  top: calc(-6 * var(--u));
  left: calc(-6 * var(--u));
  width: calc(12 * var(--u));
  height: calc(12 * var(--u));
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, rgb(var(--glow)) 25%, #fff) 0 22%, rgb(var(--glow)) 42%, rgb(var(--glow) / 0) 72%);
  animation: face-you 18s linear infinite;
}

.mid b { --glow: 255 77 157; }
.mid b:nth-of-type(5) { --glow: 255 181 71; } /* the very centre */

@keyframes spin {
  from { transform: rotateZ(-8deg) rotateX(-22deg) rotateY(0deg); }
  to   { transform: rotateZ(-8deg) rotateX(-22deg) rotateY(360deg); }
}

/* y = 0 for the middle layer, so it stays put */
@keyframes breathe {
  to { transform: translateY(calc(var(--y) * var(--step) * 1.3)); }
}

/* (step + 0.3 step) / step: exactly what the outer layers moved */
@keyframes stretch {
  to { transform: scaleY(1.3); }
}

@keyframes face-you {
  from { transform: translate3d(calc(var(--x) * var(--step)), 0, calc(var(--z) * var(--step))) rotateY(0deg); }
  to   { transform: translate3d(calc(var(--x) * var(--step)), 0, calc(var(--z) * var(--step))) rotateY(-360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.