20 CSS Loaders 05 / 20
CSS Bouncing Balls Loader
Four kinetic ball-physics loaders — a staggered bounce row, elastic scale pulses, rolling balls with rotation, and a squash-and-stretch ball with shadow — each using pure CSS animation.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="ld-05">
<div class="ld-05__stage">
<div class="ld-05__cell"><div class="ld-05__simple"><span></span><span></span><span></span><span></span></div><span class="ld-05__label">Bounce Row</span></div>
<div class="ld-05__cell"><div class="ld-05__elastic"><span></span><span></span><span></span></div><span class="ld-05__label">Elastic Pulse</span></div>
<div class="ld-05__cell"><div class="ld-05__roll"><span></span><span></span><span></span></div><span class="ld-05__label">Rolling Balls</span></div>
<div class="ld-05__cell"><div class="ld-05__squash"><div class="ld-05__squash-ball"></div><div class="ld-05__squash-shadow"></div></div> <div class="ld-05">
<div class="ld-05__stage">
<div class="ld-05__cell"><div class="ld-05__simple"><span></span><span></span><span></span><span></span></div><span class="ld-05__label">Bounce Row</span></div>
<div class="ld-05__cell"><div class="ld-05__elastic"><span></span><span></span><span></span></div><span class="ld-05__label">Elastic Pulse</span></div>
<div class="ld-05__cell"><div class="ld-05__roll"><span></span><span></span><span></span></div><span class="ld-05__label">Rolling Balls</span></div>
<div class="ld-05__cell"><div class="ld-05__squash"><div class="ld-05__squash-ball"></div><div class="ld-05__squash-shadow"></div></div>.ld-05,.ld-05 *,.ld-05 *::before,.ld-05 *::after{box-sizing:border-box;margin:0;padding:0}
.ld-05{
--bg:#1a0a2e;--c1:#ff6b6b;--c2:#ffd93d;--c3:#6bcb77;--c4:#4d96ff;--c5:#c77dff;
background:var(--bg);display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Segoe UI',sans-serif;
}
.ld-05__stage{display:flex;gap:64px;flex-wrap:wrap;justify-content:center;padding:40px}
.ld-05__cell{display:flex;flex-direction:column;align-items:center;gap:20px}
.ld-05__label{color:rgba(255,255,255,.4);font-size:11px;letter-spacing:1.5px;text-transform:uppercase}
/* Simple bounce */
.ld-05__simple{display:flex;gap:8px;align-items:flex-end;height:50px}
.ld-05__simple span{width:14px;height:14px;border-radius:50%;background:var(--c1);animation:ld-05-simple-bounce 0.8s ease-in-out infinite alternate}
.ld-05__simple span:nth-child(1){animation-delay:0s}
.ld-05__simple span:nth-child(2){animation-delay:.15s;background:var(--c2)}
.ld-05__simple span:nth-child(3){animation-delay:.3s;background:var(--c3)}
.ld-05__simple span:nth-child(4){animation-delay:.45s;background:var(--c4)}
@keyframes ld-05-simple-bounce{0%{transform:translateY(0) scaleX(1)}100%{transform:translateY(-30px) scaleX(.85)}}
/* Elastic */
.ld-05__elastic{display:flex;gap:10px;align-items:center;height:50px}
.ld-05__elastic span{width:14px;height:14px;border-radius:50%;background:var(--c4);animation:ld-05-elastic 1.2s cubic-bezier(.68,-.55,.27,1.55) infinite}
.ld-05__elastic span:nth-child(1){animation-delay:0s}
.ld-05__elastic span:nth-child(2){animation-delay:.15s;background:var(--c5)}
.ld-05__elastic span:nth-child(3){animation-delay:.3s;background:var(--c1)}
@keyframes ld-05-elastic{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.6);opacity:.5}}
/* Rolling */
.ld-05__roll{width:100px;height:30px;position:relative;overflow:hidden}
.ld-05__roll span{position:absolute;width:16px;height:16px;border-radius:50%;top:7px;background:var(--c2);animation:ld-05-roll 1.5s linear infinite}
.ld-05__roll span:nth-child(1){animation-delay:0s}
.ld-05__roll span:nth-child(2){animation-delay:.5s;background:var(--c3)}
.ld-05__roll span:nth-child(3){animation-delay:1s;background:var(--c5)}
@keyframes ld-05-roll{0%{left:-16px;transform:rotate(0)}100%{left:100%;transform:rotate(720deg)}}
/* Shadow squash */
.ld-05__squash{display:flex;flex-direction:column;align-items:center;gap:4px;height:60px;justify-content:flex-end}
.ld-05__squash-ball{width:20px;height:20px;border-radius:50%;background:var(--c1);animation:ld-05-squash-ball 0.8s ease-in-out infinite alternate}
.ld-05__squash-shadow{width:20px;height:6px;border-radius:50%;background:rgba(255,107,107,.3);animation:ld-05-squash-shadow 0.8s ease-in-out infinite alternate}
@keyframes ld-05-squash-ball{0%{transform:translateY(0) scaleY(1)}100%{transform:translateY(-28px) scaleY(.9)}}
@keyframes ld-05-squash-shadow{0%{transform:scaleX(1);opacity:.4}100%{transform:scaleX(.5);opacity:.1}}
@media(prefers-reduced-motion:reduce){
.ld-05__simple span,.ld-05__elastic span,.ld-05__roll span,.ld-05__squash-ball,.ld-05__squash-shadow{animation:none}
} .ld-05,.ld-05 *,.ld-05 *::before,.ld-05 *::after{box-sizing:border-box;margin:0;padding:0}
.ld-05{
--bg:#1a0a2e;--c1:#ff6b6b;--c2:#ffd93d;--c3:#6bcb77;--c4:#4d96ff;--c5:#c77dff;
background:var(--bg);display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Segoe UI',sans-serif;
}
.ld-05__stage{display:flex;gap:64px;flex-wrap:wrap;justify-content:center;padding:40px}
.ld-05__cell{display:flex;flex-direction:column;align-items:center;gap:20px}
.ld-05__label{color:rgba(255,255,255,.4);font-size:11px;letter-spacing:1.5px;text-transform:uppercase}
/* Simple bounce */
.ld-05__simple{display:flex;gap:8px;align-items:flex-end;height:50px}
.ld-05__simple span{width:14px;height:14px;border-radius:50%;background:var(--c1);animation:ld-05-simple-bounce 0.8s ease-in-out infinite alternate}
.ld-05__simple span:nth-child(1){animation-delay:0s}
.ld-05__simple span:nth-child(2){animation-delay:.15s;background:var(--c2)}
.ld-05__simple span:nth-child(3){animation-delay:.3s;background:var(--c3)}
.ld-05__simple span:nth-child(4){animation-delay:.45s;background:var(--c4)}
@keyframes ld-05-simple-bounce{0%{transform:translateY(0) scaleX(1)}100%{transform:translateY(-30px) scaleX(.85)}}
/* Elastic */
.ld-05__elastic{display:flex;gap:10px;align-items:center;height:50px}
.ld-05__elastic span{width:14px;height:14px;border-radius:50%;background:var(--c4);animation:ld-05-elastic 1.2s cubic-bezier(.68,-.55,.27,1.55) infinite}
.ld-05__elastic span:nth-child(1){animation-delay:0s}
.ld-05__elastic span:nth-child(2){animation-delay:.15s;background:var(--c5)}
.ld-05__elastic span:nth-child(3){animation-delay:.3s;background:var(--c1)}
@keyframes ld-05-elastic{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.6);opacity:.5}}
/* Rolling */
.ld-05__roll{width:100px;height:30px;position:relative;overflow:hidden}
.ld-05__roll span{position:absolute;width:16px;height:16px;border-radius:50%;top:7px;background:var(--c2);animation:ld-05-roll 1.5s linear infinite}
.ld-05__roll span:nth-child(1){animation-delay:0s}
.ld-05__roll span:nth-child(2){animation-delay:.5s;background:var(--c3)}
.ld-05__roll span:nth-child(3){animation-delay:1s;background:var(--c5)}
@keyframes ld-05-roll{0%{left:-16px;transform:rotate(0)}100%{left:100%;transform:rotate(720deg)}}
/* Shadow squash */
.ld-05__squash{display:flex;flex-direction:column;align-items:center;gap:4px;height:60px;justify-content:flex-end}
.ld-05__squash-ball{width:20px;height:20px;border-radius:50%;background:var(--c1);animation:ld-05-squash-ball 0.8s ease-in-out infinite alternate}
.ld-05__squash-shadow{width:20px;height:6px;border-radius:50%;background:rgba(255,107,107,.3);animation:ld-05-squash-shadow 0.8s ease-in-out infinite alternate}
@keyframes ld-05-squash-ball{0%{transform:translateY(0) scaleY(1)}100%{transform:translateY(-28px) scaleY(.9)}}
@keyframes ld-05-squash-shadow{0%{transform:scaleX(1);opacity:.4}100%{transform:scaleX(.5);opacity:.1}}
@media(prefers-reduced-motion:reduce){
.ld-05__simple span,.ld-05__elastic span,.ld-05__roll span,.ld-05__squash-ball,.ld-05__squash-shadow{animation:none}
}How this works
The bounce row uses animation-direction:alternate with ease-in-out so balls glide up and ease back down naturally. The elastic variant applies cubic-bezier(.68,-.55,.27,1.55) — the classic overshoot curve — to a scale(1.6) keyframe so each ball momentarily grows past full size before settling, mimicking a spring. The rolling ball uses a combined translateX and rotate(720deg) in a single transform value, advancing and spinning together so the rotation matches the travel distance.
The squash-and-stretch ball pairs two animations: the ball itself runs translateY up and a subtle scaleY(.9) compression, while a sibling shadow element does the inverse — scaleX shrinks and opacity drops as the ball rises. Both share the same duration and animation-direction:alternate so they stay in sync without JS.
Customize
- Speed up the bounce row by reducing
animation-durationfrom0.8sto0.5s— reduce the delay step proportionally to keep the chase feeling tight. - Exaggerate the elastic overshoot by increasing the cubic-bezier overshoot from
-.55to-.8— this creates a more comedic spring effect. - Change ball colours per
nth-childselector — a monochrome white-to-grey ramp looks polished on dark card surfaces. - Adjust the rolling ball speed by changing the
left:-16px → left:100%range and matching the rotation degrees so one full ball circumference equals 360deg of rotation. - Add a floor line using a thin
border-bottomon the container so the bounce row has a visual baseline, making the physics read more clearly.
Watch out for
- The rolling ball uses
overflow:hiddenon the track — ensure the track has explicitwidthor the balls will clip at unpredictable positions. - The squash shadow must be a sibling element in the same flex/grid column as the ball to maintain vertical alignment — nesting it inside the ball breaks the layout.
animation-direction:alternatereads the keyframe in reverse on even iterations — if your 0% → 100% keyframe isn't visually symmetric, the return trip will look wrong.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 9+ | 44+ | 49+ |
Cubic-bezier overshoot is universally supported; all techniques use standard CSS animations.