Loaders & patterns
Flipping loader
Pure CSSThe classic square loader: half a turn on X, then half a turn on Y.
Rungs spin on negative delays; dots counter-rotate so they always face the camera.
Your edited version
rotateY animation.animation-delay per rung starts each one part-way through its turn — that offset is the twist.reverse. The two rotations cancel and the dot always faces the camera (a "billboard").preserve-3d for that cancellation to happen in 3D space.negative animation-delaybillboard counter-rotationSass @for<div class="scene">
<div class="helix">
<div style="--i:1"><i></i><i></i></div>
<div style="--i:2"><i></i><i></i></div>
<div style="--i:3"><i></i><i></i></div>
<div style="--i:4"><i></i><i></i></div>
<div style="--i:5"><i></i><i></i></div>
<div style="--i:6"><i></i><i></i></div>
<div style="--i:7"><i></i><i></i></div>
<div style="--i:8"><i></i><i></i></div>
<div style="--i:9"><i></i><i></i></div>
<div style="--i:10"><i></i><i></i></div>
<div style="--i:11"><i></i><i></i></div>
<div style="--i:12"><i></i><i></i></div>
</div>
</div>
.scene {
perspective: 800px;
}
.helix {
display: grid;
gap: 12px;
transform-style: preserve-3d;
transform: rotateZ(-14deg);
}
.helix > div {
--delay: calc(var(--i) * -0.22s);
position: relative;
width: 120px;
height: 3px;
background: linear-gradient(90deg, #2ee6d6, transparent 35% 65%, #ff4d9d);
transform-style: preserve-3d;
animation: helix-spin 4s linear infinite;
animation-delay: var(--delay);
}
.helix i {
position: absolute;
top: -6px;
width: 15px;
height: 15px;
border-radius: 50%;
/* same animation, reversed → the dot always faces the camera */
animation: helix-spin 4s linear infinite reverse;
animation-delay: var(--delay);
}
.helix i:first-child { left: -7px; background: #2ee6d6; box-shadow: 0 0 10px #2ee6d6; }
.helix i:last-child { right: -7px; background: #ff4d9d; box-shadow: 0 0 10px #ff4d9d; }
@keyframes helix-spin {
to { transform: rotateY(360deg); }
}
$rungs: 14;
$period: 4s;
.d-helix {
display: grid;
gap: 9px;
transform-style: preserve-3d;
transform: rotateZ(-14deg);
&__rung {
position: relative;
width: 96px;
height: 3px;
border-radius: 3px;
background: linear-gradient(90deg, var(--accent-2), transparent 35% 65%, var(--hot));
transform-style: preserve-3d;
animation: d-helix-spin $period linear infinite;
animation-delay: var(--delay);
// Negative delays start every rung part-way through the turn → the twist.
@for $i from 1 through $rungs {
&:nth-child(#{$i}) {
--delay: #{$i * -0.22s};
}
}
}
i {
position: absolute;
top: -5px;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--accent-2);
box-shadow: 0 0 10px currentColor;
color: var(--accent-2);
// Billboard: spin the opposite way so the dot always faces the camera.
animation: d-helix-spin $period linear infinite reverse;
animation-delay: var(--delay);
&:first-child {
left: -6px;
}
&:last-child {
right: -6px;
background: var(--hot);
color: var(--hot);
}
}
}
@keyframes d-helix-spin {
to {
transform: rotateY(360deg);
}
}