Loaders & patterns
Flipping loader
Pure CSSThe classic square loader: half a turn on X, then half a turn on Y.
JS builds the grid and gives each cell its distance from the centre; CSS turns that into a ripple.
Your edited version
--d.translateZ.animation-delay: calc(var(--d) * -0.22s) offsets each cell by its distance, so the motion reads as a ripple spreading outward.generated DOManimation-delay from distanceone shared @keyframes<div class="scene">
<div class="grid"></div>
</div>
.scene {
perspective: 800px;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--n), 1fr);
gap: 4px;
width: 260px;
height: 260px;
transform-style: preserve-3d;
transform: rotateX(58deg) rotateZ(-45deg);
}
.grid i {
border-radius: 3px;
background: #ffb547;
animation: bob 2.4s ease-in-out infinite;
animation-delay: calc(var(--d) * -0.22s);
}
/* opacity + transform only: both run on the compositor, even with 100+ cells */
@keyframes bob {
0%, 100% { transform: translateZ(-14px); opacity: 0.4; }
50% { transform: translateZ(40px); opacity: 1; }
}
const grid = document.querySelector('.grid');
const N = 11;
const mid = (N - 1) / 2;
grid.style.setProperty('--n', N);
for (let row = 0; row < N; row++) {
for (let col = 0; col < N; col++) {
const cell = document.createElement('i');
cell.style.setProperty('--d', Math.hypot(row - mid, col - mid).toFixed(2));
grid.append(cell);
}
}
.d-wavegrid {
display: grid;
grid-template-rows: 1fr auto;
width: 100%;
height: 100%;
padding: 8px 14px 10px;
&__view {
display: grid;
place-items: center;
perspective: 700px;
}
&__grid {
// --n (columns) is set by JS together with the cells
display: grid;
grid-template-columns: repeat(var(--n), 1fr);
gap: 3px;
width: 170px;
height: 170px;
transform-style: preserve-3d;
transform: rotateX(58deg) rotateZ(-45deg);
i {
border-radius: 3px;
background: var(--warm);
animation: d-wavegrid-bob 2.4s ease-in-out infinite;
// --d = distance from the centre, so the ripple travels outward
animation-delay: calc(var(--d) * -0.22s);
}
}
label {
display: flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-family: var(--mono);
font-size: 11px;
}
output {
min-width: 5ch;
color: var(--text);
}
input {
flex: 1;
accent-color: var(--warm);
}
}
@keyframes d-wavegrid-bob {
0%,
100% {
opacity: 0.4;
transform: translateZ(-14px);
}
50% {
opacity: 1;
transform: translateZ(34px);
}
}