25 CSS Spinners 15 / 25
Bouncing Elastic Dots Spinner
Three coloured dots bounce with spring-like squash and stretch physics — compressing horizontally and elongating vertically at peak height — using a multi-step cubic-bezier keyframe.
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="sp-15">
<div class="sp-15__row">
<div class="sp-15__dot"></div>
<div class="sp-15__dot"></div>
<div class="sp-15__dot"></div>
</div>
</div> <div class="sp-15">
<div class="sp-15__row">
<div class="sp-15__dot"></div>
<div class="sp-15__dot"></div>
<div class="sp-15__dot"></div>
</div>
</div>.sp-15,.sp-15 *,.sp-15 *::before,.sp-15 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-15{
--bg:#0e0b1e;
--c1:#ff6e6c;
--c2:#cbacf9;
--c3:#67e8f9;
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-15__row{
display:flex;
align-items:flex-end;
gap:10px;
height:70px;
}
.sp-15__dot{
width:18px;
height:18px;
border-radius:50%;
animation:sp-15-bounce 0.9s cubic-bezier(0.36,0.07,0.19,0.97) infinite;
}
.sp-15__dot:nth-child(1){
background:var(--c1);
box-shadow:0 0 10px var(--c1);
animation-delay:0s;
}
.sp-15__dot:nth-child(2){
background:var(--c2);
box-shadow:0 0 10px var(--c2);
animation-delay:0.15s;
}
.sp-15__dot:nth-child(3){
background:var(--c3);
box-shadow:0 0 10px var(--c3);
animation-delay:0.3s;
}
@keyframes sp-15-bounce{
0%,100%{transform:translateY(0) scaleX(1) scaleY(1)}
30%{transform:translateY(-40px) scaleX(0.9) scaleY(1.1)}
50%{transform:translateY(-48px) scaleX(0.85) scaleY(1.15)}
70%{transform:translateY(-40px) scaleX(0.9) scaleY(1.1)}
85%{transform:translateY(-4px) scaleX(1.1) scaleY(0.9)}
}
@media (prefers-reduced-motion: reduce){
.sp-15__dot{animation:none}
} .sp-15,.sp-15 *,.sp-15 *::before,.sp-15 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-15{
--bg:#0e0b1e;
--c1:#ff6e6c;
--c2:#cbacf9;
--c3:#67e8f9;
display:flex;
align-items:center;
justify-content:center;
min-height:100vh;
background:var(--bg);
}
.sp-15__row{
display:flex;
align-items:flex-end;
gap:10px;
height:70px;
}
.sp-15__dot{
width:18px;
height:18px;
border-radius:50%;
animation:sp-15-bounce 0.9s cubic-bezier(0.36,0.07,0.19,0.97) infinite;
}
.sp-15__dot:nth-child(1){
background:var(--c1);
box-shadow:0 0 10px var(--c1);
animation-delay:0s;
}
.sp-15__dot:nth-child(2){
background:var(--c2);
box-shadow:0 0 10px var(--c2);
animation-delay:0.15s;
}
.sp-15__dot:nth-child(3){
background:var(--c3);
box-shadow:0 0 10px var(--c3);
animation-delay:0.3s;
}
@keyframes sp-15-bounce{
0%,100%{transform:translateY(0) scaleX(1) scaleY(1)}
30%{transform:translateY(-40px) scaleX(0.9) scaleY(1.1)}
50%{transform:translateY(-48px) scaleX(0.85) scaleY(1.15)}
70%{transform:translateY(-40px) scaleX(0.9) scaleY(1.1)}
85%{transform:translateY(-4px) scaleX(1.1) scaleY(0.9)}
}
@media (prefers-reduced-motion: reduce){
.sp-15__dot{animation:none}
}How this works
Each dot uses a five-step sp-15-bounce keyframe that combines translateY (vertical movement) with scaleX and scaleY for squash-and-stretch: at launch the dot compresses slightly wide, narrows as it rises, reaches peak height with a tall-and-thin shape, then re-compresses on landing. The timing function cubic-bezier(0.36,0.07,0.19,0.97) applies a sharp ease-in (slow start → fast up) and ease-out (fast → slow at peak) that mimics gravity.
Three dots are staggered by 0s, 0.15s, and 0.3s delays, each in a distinct colour. The dots sit in a flex row with align-items:flex-end so the stationary baseline and the vertical bounce are both clearly readable.
Customize
- Edit
--c1,--c2,--c3to change dot colours — a monochrome white-to-grey gradient suits minimal dark UIs. - Increase bounce height from
-48pxto-70pxfor a more dramatic arc. - Reduce the squash/stretch values (e.g.
scaleX(0.95)instead of0.85) for a subtler, less cartoonish bounce. - Add a fourth dot at
animation-delay:0.45sfor a four-dot rolling bounce pattern. - Change dot size from
18pxto10pxfor a compact three-dot ellipsis-style loading indicator.
Watch out for
scaleXandscaleYat the same keyframe step will compound with any parent transform — if dots are inside a scaled container, the squash values will be skewed.- The
cubic-beziertiming function applies to the entire keyframe interpolation, not individual steps — large bounces with a single easing can feel mechanically off if the peak and floor transitions mismatch; split into separate up/down animations for precise control. align-items:flex-endgrounds the dots to the bottom of the flex container — if the container height is less than the bounce height (70px), dots will clip; ensure container height is at leastmax-translate + dot-size.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
Compositor-based transform animation; no modern-only features required.