3D equalizer
Pure CSSSeven real cuboids bouncing like an audio meter. Walls are squashed with scaleY and each lid rides down by the same amount, so nothing but transforms ever changes.
Eight blocks pop into the air and drop one by one onto an isometric floor until they form a 2×2×2 cube, then the cube comes apart column by column and floats away, and it all starts again.
Your edited version
rotateX(58deg) rotateZ(45deg) turns a flat 2×2 grid into an isometric floor; from then on "up" is simply translateZ. A block only needs the three faces this camera can ever see: the top, the +x side and the +y side.animation-delay from --i (block 0…7) and --c (column 0…3), so eight blocks need only one "arrive" and one "leave" animation.translateZ … scale3d), and scale3d(0, 0, 0) rather than scale(0), because a 2D scale would leave a block's height standing as a line.--u, tied to the canvas: a block is 34 units, and it drops in from 20 above its place, low enough that a drop onto the top layer stays inside the canvas, so the stack is the same share of a gallery card, the editor and a recording canvas.rotateX(58deg) rotateZ(45deg) = isometric floorblock = 3 visible faces onlytwo nested animations: block drops, column leavesshared keyframes + delays from --i<div class="scene">
<div class="stack" role="img" aria-label="Loading">
<div class="col" style="--c:0">
<div class="block" style="--i:0; --l:0"><i></i><i></i><i></i></div>
<div class="block" style="--i:4; --l:1"><i></i><i></i><i></i></div>
</div>
<div class="col" style="--c:1">
<div class="block" style="--i:1; --l:0"><i></i><i></i><i></i></div>
<div class="block" style="--i:5; --l:1"><i></i><i></i><i></i></div>
</div>
<div class="col" style="--c:2">
<div class="block" style="--i:2; --l:0"><i></i><i></i><i></i></div>
<div class="block" style="--i:6; --l:1"><i></i><i></i><i></i></div>
</div>
<div class="col" style="--c:3">
<div class="block" style="--i:3; --l:0"><i></i><i></i><i></i></div>
<div class="block" style="--i:7; --l:1"><i></i><i></i><i></i></div>
</div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the stack is the same share of a
gallery card, the editor, a full screen and a recording canvas */
--u: 0.44vmin;
perspective: calc(800 * var(--u));
}
/* the floor is the stack's own plane, turned isometric; "up" is translateZ.
The blocks and the columns all fly upwards and nothing ever goes below the plate, so the
drawing sits well above its own layout box. translateY pushes the plate back down by that
much, which centres what is actually drawn rather than what is laid out: the solid blocks,
over the whole loop, since the eye places the stack by them and not by the faint plate. */
.stack {
position: relative;
display: grid;
grid-template-columns: repeat(2, calc(38 * var(--u)));
grid-auto-rows: calc(38 * var(--u));
transform-style: preserve-3d;
transform: translateY(calc(30 * var(--u))) rotateX(58deg) rotateZ(45deg);
}
/* the floor plate with the four landing places */
.stack::before {
content: '';
position: absolute;
inset: calc(-8 * var(--u));
border: calc(1 * var(--u)) dashed rgb(140 150 220 / 0.34);
border-radius: calc(10 * var(--u));
background:
linear-gradient(90deg, transparent calc(50% - 1 * var(--u)), rgb(140 150 220 / 0.16) 0 calc(50% + 1 * var(--u)), transparent 0),
linear-gradient(0deg, transparent calc(50% - 1 * var(--u)), rgb(140 150 220 / 0.16) 0 calc(50% + 1 * var(--u)), transparent 0),
rgb(139 108 255 / 0.1);
transform: translateZ(calc(-1 * var(--u)));
}
/* a column: lifts away once the cube is complete, one after the other */
.col {
position: relative;
margin: calc(2 * var(--u));
transform-style: preserve-3d;
transform-origin: 50% 50% calc(34 * var(--u)); /* the middle of the two-block column */
animation: stack-leave 5s linear infinite;
/* 3s further on than the blocks' own start, like them (see .block) */
animation-delay: calc(var(--c) * 0.275s - 4.6s);
}
/* a block: pops up in the air, falls, bounces, sits; bottom layer first */
.block {
--z: calc(var(--l) * 34 * var(--u)); /* resting height */
--color: #8b6cff;
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform-origin: 50% 50% calc(17 * var(--u));
animation: stack-arrive 5s linear infinite;
/* 3s into the loop on load: the cube is built and has not begun to come apart, so the stack
looks finished when it is paused, and every block is already mid-cycle */
animation-delay: calc(var(--i) * 0.275s - 3s);
}
.block:nth-child(2) { --color: #2ee6d6; }
/* only the three faces we can ever see */
.block i {
position: absolute;
inset: 0;
border-radius: calc(2 * var(--u));
}
.block i:nth-child(1) { /* top */
background: color-mix(in srgb, var(--color) 55%, #fff);
transform: translateZ(calc(34 * var(--u)));
}
.block i:nth-child(2) { /* +x side, stood up on its right edge */
background: color-mix(in srgb, var(--color) 65%, #000);
transform-origin: 100% 50%;
transform: rotateY(90deg);
}
.block i:nth-child(3) { /* +y side, stood up on its bottom edge */
background: var(--color);
transform-origin: 50% 100%;
transform: rotateX(-90deg);
}
@keyframes stack-arrive {
0% {
transform: translateZ(calc(var(--z) + 20 * var(--u))) scale3d(0, 0, 0);
animation-timing-function: ease-out;
}
3% {
transform: translateZ(calc(var(--z) + 20 * var(--u))) scale3d(1, 1, 1);
animation-timing-function: ease-in; /* gravity */
}
10% {
transform: translateZ(var(--z)) scale3d(1, 1, 1);
animation-timing-function: ease-out;
}
12.5% {
transform: translateZ(calc(var(--z) + 5 * var(--u))) scale3d(1, 1, 1);
animation-timing-function: ease-in;
}
15%, 76.5% {
transform: translateZ(var(--z)) scale3d(1, 1, 1);
}
/* its column is gone by now: reset out of sight */
76.6%, 100% {
transform: translateZ(calc(var(--z) + 20 * var(--u))) scale3d(0, 0, 0);
}
}
@keyframes stack-leave {
0% {
transform: translateZ(0) rotateZ(0deg) scale3d(1, 1, 1);
animation-timing-function: ease-in;
}
7%, 31.25% {
transform: translateZ(calc(56 * var(--u))) rotateZ(90deg) scale3d(0, 0, 0);
}
/* both its blocks have reset by now: come back, still empty */
31.35%, 100% {
transform: translateZ(0) rotateZ(0deg) scale3d(1, 1, 1);
}
}