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.

Pure CSS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

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>
.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-duration from 0.8s to 0.5s — reduce the delay step proportionally to keep the chase feeling tight.
  • Exaggerate the elastic overshoot by increasing the cubic-bezier overshoot from -.55 to -.8 — this creates a more comedic spring effect.
  • Change ball colours per nth-child selector — 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-bottom on the container so the bounce row has a visual baseline, making the physics read more clearly.

Watch out for

  • The rolling ball uses overflow:hidden on the track — ensure the track has explicit width or 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:alternate reads the keyframe in reverse on even iterations — if your 0% → 100% keyframe isn't visually symmetric, the return trip will look wrong.

Browser support

ChromeSafariFirefoxEdge
49+ 9+ 44+ 49+

Cubic-bezier overshoot is universally supported; all techniques use standard CSS animations.

Search CodeFronts

Loading…