16 CSS Bounce Animations11 / 16
Physics Ball Squash Stretch
A bouncing ball with realistic squash-and-stretch physics — falls fast, flattens on floor contact, rebounds with elastic stretch. The classic Disney animation principle rendered as pure CSS with a multi-stop keyframe. Coral ball on a warm beige floor with a soft ground shadow that scales with the ball's height. The pattern users search for when learning "how do I bounce a ball with real physics in CSS."
Published
The code
<div class="bn-11">
<p class="bn-11__label">Squash-and-stretch physics ball</p>
<div class="bn-11__scene" aria-hidden="true">
<div class="bn-11__ball"></div>
<div class="bn-11__shadow"></div>
<div class="bn-11__floor"></div>
</div>
<p class="bn-11__hint">Ball stretches at descent + rebound, squashes at floor contact.</p>
</div><div class="bn-11">
<p class="bn-11__label">Squash-and-stretch physics ball</p>
<div class="bn-11__scene" aria-hidden="true">
<div class="bn-11__ball"></div>
<div class="bn-11__shadow"></div>
<div class="bn-11__floor"></div>
</div>
<p class="bn-11__hint">Ball stretches at descent + rebound, squashes at floor contact.</p>
</div>.bn-11 {
--page-bg: #faf7f0;
--floor: #c9b78c;
--ball: #ff6b6b;
--ball-hi: #ffa8a8;
--ball-shadow: rgba(191,64,64,.35);
--shadow-color: rgba(58,42,26,.35);
--ink: #3a2a1a;
--sub: #8b7355;
font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(80% 60% at 50% 100%, rgba(191,157,111,.24) 0%, transparent 55%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-11 *,
.bn-11 *::before,
.bn-11 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-11 ::selection {
background: var(--ball);
color: #fff;
}
.bn-11__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .02em;
}
.bn-11__scene {
position: relative;
width: 200px;
height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.bn-11__ball {
position: absolute;
bottom: 32px;
left: 50%;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%,var(--ball-hi),var(--ball));
box-shadow: 0 4px 8px -2px var(--ball-shadow);
transform: translateX(-50%);
animation: bn-11-ball 1.2s ease-in-out infinite;
transform-origin: bottom center;
will-change: transform;
}
@keyframes bn-11-ball {
0% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
45% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
50% {
transform: translateX(-50%) translateY(0) scaleY(.7) scaleX(1.3);
}
55% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
100% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
}
.bn-11__shadow {
position: absolute;
bottom: 24px;
left: 50%;
width: 60px;
height: 16px;
border-radius: 50%;
background: var(--shadow-color);
filter: blur(4px);
transform: translateX(-50%) scale(.5);
animation: bn-11-shadow 1.2s ease-in-out infinite;
will-change: transform;
}
@keyframes bn-11-shadow {
0% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
50% {
transform: translateX(-50%) scale(1.15);
opacity: .85;
}
100% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
}
.bn-11__floor {
width: 180px;
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,transparent,var(--floor) 20%,var(--floor) 80%,transparent);
}
.bn-11__hint {
font-size: .78rem;
color: var(--sub);
max-width: 360px;
line-height: 1.5;
}
@media (prefers-reduced-motion: reduce) {
.bn-11__ball,
.bn-11__shadow {
animation: none!important;
}
.bn-11__ball {
transform: translateX(-50%) translateY(0) scale(1)!important;
}
.bn-11__shadow {
transform: translateX(-50%) scale(1)!important;
opacity: .7!important;
}
}.bn-11 {
--page-bg: #faf7f0;
--floor: #c9b78c;
--ball: #ff6b6b;
--ball-hi: #ffa8a8;
--ball-shadow: rgba(191,64,64,.35);
--shadow-color: rgba(58,42,26,.35);
--ink: #3a2a1a;
--sub: #8b7355;
font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(80% 60% at 50% 100%, rgba(191,157,111,.24) 0%, transparent 55%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-11 *,
.bn-11 *::before,
.bn-11 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-11 ::selection {
background: var(--ball);
color: #fff;
}
.bn-11__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .02em;
}
.bn-11__scene {
position: relative;
width: 200px;
height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.bn-11__ball {
position: absolute;
bottom: 32px;
left: 50%;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%,var(--ball-hi),var(--ball));
box-shadow: 0 4px 8px -2px var(--ball-shadow);
transform: translateX(-50%);
animation: bn-11-ball 1.2s ease-in-out infinite;
transform-origin: bottom center;
will-change: transform;
}
@keyframes bn-11-ball {
0% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
45% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
50% {
transform: translateX(-50%) translateY(0) scaleY(.7) scaleX(1.3);
}
55% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
100% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
}
.bn-11__shadow {
position: absolute;
bottom: 24px;
left: 50%;
width: 60px;
height: 16px;
border-radius: 50%;
background: var(--shadow-color);
filter: blur(4px);
transform: translateX(-50%) scale(.5);
animation: bn-11-shadow 1.2s ease-in-out infinite;
will-change: transform;
}
@keyframes bn-11-shadow {
0% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
50% {
transform: translateX(-50%) scale(1.15);
opacity: .85;
}
100% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
}
.bn-11__floor {
width: 180px;
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,transparent,var(--floor) 20%,var(--floor) 80%,transparent);
}
.bn-11__hint {
font-size: .78rem;
color: var(--sub);
max-width: 360px;
line-height: 1.5;
}
@media (prefers-reduced-motion: reduce) {
.bn-11__ball,
.bn-11__shadow {
animation: none!important;
}
.bn-11__ball {
transform: translateX(-50%) translateY(0) scale(1)!important;
}
.bn-11__shadow {
transform: translateX(-50%) scale(1)!important;
opacity: .7!important;
}
}Here's a working CSS Bounce Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Physics Ball Squash Stretch
Source: https://codefronts.com/motion/css-bounce-animation/physics-ball-squash-stretch/
A bouncing ball with realistic squash-and-stretch physics — falls fast, flattens on floor contact, rebounds with elastic stretch. The classic Disney animation principle rendered as pure CSS with a multi-stop keyframe. Coral ball on a warm beige floor with a soft ground shadow that scales with the ball's height. The pattern users search for when learning "how do I bounce a ball with real physics in CSS."
## HTML
```html
<div class="bn-11">
<p class="bn-11__label">Squash-and-stretch physics ball</p>
<div class="bn-11__scene" aria-hidden="true">
<div class="bn-11__ball"></div>
<div class="bn-11__shadow"></div>
<div class="bn-11__floor"></div>
</div>
<p class="bn-11__hint">Ball stretches at descent + rebound, squashes at floor contact.</p>
</div>
```
## CSS
```css
.bn-11 {
--page-bg: #faf7f0;
--floor: #c9b78c;
--ball: #ff6b6b;
--ball-hi: #ffa8a8;
--ball-shadow: rgba(191,64,64,.35);
--shadow-color: rgba(58,42,26,.35);
--ink: #3a2a1a;
--sub: #8b7355;
font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(80% 60% at 50% 100%, rgba(191,157,111,.24) 0%, transparent 55%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-11 *,
.bn-11 *::before,
.bn-11 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-11 ::selection {
background: var(--ball);
color: #fff;
}
.bn-11__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .02em;
}
.bn-11__scene {
position: relative;
width: 200px;
height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.bn-11__ball {
position: absolute;
bottom: 32px;
left: 50%;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%,var(--ball-hi),var(--ball));
box-shadow: 0 4px 8px -2px var(--ball-shadow);
transform: translateX(-50%);
animation: bn-11-ball 1.2s ease-in-out infinite;
transform-origin: bottom center;
will-change: transform;
}
@keyframes bn-11-ball {
0% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
45% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
50% {
transform: translateX(-50%) translateY(0) scaleY(.7) scaleX(1.3);
}
55% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
100% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
}
.bn-11__shadow {
position: absolute;
bottom: 24px;
left: 50%;
width: 60px;
height: 16px;
border-radius: 50%;
background: var(--shadow-color);
filter: blur(4px);
transform: translateX(-50%) scale(.5);
animation: bn-11-shadow 1.2s ease-in-out infinite;
will-change: transform;
}
@keyframes bn-11-shadow {
0% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
50% {
transform: translateX(-50%) scale(1.15);
opacity: .85;
}
100% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
}
.bn-11__floor {
width: 180px;
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,transparent,var(--floor) 20%,var(--floor) 80%,transparent);
}
.bn-11__hint {
font-size: .78rem;
color: var(--sub);
max-width: 360px;
line-height: 1.5;
}
@media (prefers-reduced-motion: reduce) {
.bn-11__ball,
.bn-11__shadow {
animation: none!important;
}
.bn-11__ball {
transform: translateX(-50%) translateY(0) scale(1)!important;
}
.bn-11__shadow {
transform: translateX(-50%) scale(1)!important;
opacity: .7!important;
}
}
```Here's a working CSS Bounce Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Physics Ball Squash Stretch
Source: https://codefronts.com/motion/css-bounce-animation/physics-ball-squash-stretch/
A bouncing ball with realistic squash-and-stretch physics — falls fast, flattens on floor contact, rebounds with elastic stretch. The classic Disney animation principle rendered as pure CSS with a multi-stop keyframe. Coral ball on a warm beige floor with a soft ground shadow that scales with the ball's height. The pattern users search for when learning "how do I bounce a ball with real physics in CSS."
## HTML
```html
<div class="bn-11">
<p class="bn-11__label">Squash-and-stretch physics ball</p>
<div class="bn-11__scene" aria-hidden="true">
<div class="bn-11__ball"></div>
<div class="bn-11__shadow"></div>
<div class="bn-11__floor"></div>
</div>
<p class="bn-11__hint">Ball stretches at descent + rebound, squashes at floor contact.</p>
</div>
```
## CSS
```css
.bn-11 {
--page-bg: #faf7f0;
--floor: #c9b78c;
--ball: #ff6b6b;
--ball-hi: #ffa8a8;
--ball-shadow: rgba(191,64,64,.35);
--shadow-color: rgba(58,42,26,.35);
--ink: #3a2a1a;
--sub: #8b7355;
font-family: -apple-system,BlinkMacSystemFont,'Inter','Segoe UI',sans-serif;
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 32px;
background: radial-gradient(80% 60% at 50% 100%, rgba(191,157,111,.24) 0%, transparent 55%), var(--page-bg);
color: var(--ink);
text-align: center;
}
.bn-11 *,
.bn-11 *::before,
.bn-11 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bn-11 ::selection {
background: var(--ball);
color: #fff;
}
.bn-11__label {
font-size: .9rem;
font-weight: 600;
color: var(--sub);
letter-spacing: .02em;
}
.bn-11__scene {
position: relative;
width: 200px;
height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.bn-11__ball {
position: absolute;
bottom: 32px;
left: 50%;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%,var(--ball-hi),var(--ball));
box-shadow: 0 4px 8px -2px var(--ball-shadow);
transform: translateX(-50%);
animation: bn-11-ball 1.2s ease-in-out infinite;
transform-origin: bottom center;
will-change: transform;
}
@keyframes bn-11-ball {
0% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
45% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
50% {
transform: translateX(-50%) translateY(0) scaleY(.7) scaleX(1.3);
}
55% {
transform: translateX(-50%) translateY(-40px) scaleY(1.15) scaleX(.9);
}
100% {
transform: translateX(-50%) translateY(-160px) scale(1);
}
}
.bn-11__shadow {
position: absolute;
bottom: 24px;
left: 50%;
width: 60px;
height: 16px;
border-radius: 50%;
background: var(--shadow-color);
filter: blur(4px);
transform: translateX(-50%) scale(.5);
animation: bn-11-shadow 1.2s ease-in-out infinite;
will-change: transform;
}
@keyframes bn-11-shadow {
0% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
50% {
transform: translateX(-50%) scale(1.15);
opacity: .85;
}
100% {
transform: translateX(-50%) scale(.5);
opacity: .4;
}
}
.bn-11__floor {
width: 180px;
height: 6px;
border-radius: 3px;
background: linear-gradient(90deg,transparent,var(--floor) 20%,var(--floor) 80%,transparent);
}
.bn-11__hint {
font-size: .78rem;
color: var(--sub);
max-width: 360px;
line-height: 1.5;
}
@media (prefers-reduced-motion: reduce) {
.bn-11__ball,
.bn-11__shadow {
animation: none!important;
}
.bn-11__ball {
transform: translateX(-50%) translateY(0) scale(1)!important;
}
.bn-11__shadow {
transform: translateX(-50%) scale(1)!important;
opacity: .7!important;
}
}
```How this works
Two elements sync in sync: the ball and its ground shadow. The ball animates via @keyframes bn-11-ball — a 5-stop cascade combining translateY for vertical motion + scaleY / scaleX for deformation: 0% translateY(-160px) scale(1) → 45% translateY(-40px) scaleY(1.15) scaleX(.9) (stretched at descent) → 50% translateY(0) scaleY(.7) scaleX(1.3) (SQUASHED at floor) → 55% translateY(-40px) scaleY(1.15) scaleX(.9) (stretched at rebound) → 100% translateY(-160px) scale(1) (back at peak).
The squash at 50% is the KEY moment — the ball flattens horizontally as it hits the ground, mimicking rubber compression under impact. The stretch at 45% and 55% (just before and after floor contact) mimics elastic elongation in the direction of motion. This is the Disney squash-and-stretch principle in 3 keyframe stops.
Timing asymmetry mimics gravity: the descent (0-50%) uses animation-timing-function: cubic-bezier(.5, 0, 1, 1) (ease-in — accelerating toward the floor) and the ascent (50-100%) uses cubic-bezier(0, 0, .5, 1) (ease-out — decelerating toward the peak). But CSS only applies one timing function per animation, so we use ease-in-out as compromise. Duration 1.2 seconds, infinite loop.
The ground shadow animates in parallel via @keyframes bn-11-shadow — scales from small at ball's peak to wider at floor contact, then back. The shadow is a wide ellipse (60px × 12px) with heavy blur and semi-transparent black, sitting just below where the ball lands.
Make it yours
- Change the squash intensity by editing
scaleY(.7) scaleX(1.3)at 50% —.85 / 1.15is subtle (realistic rubber),.5 / 1.5is cartoonish (cartoon rubber). - Change bounce speed by editing
animation-duration: 1.2s. Faster (.8s) reads as light ball (ping-pong); slower (2s) reads as heavy ball (basketball). - For a decaying bounce (loses energy per cycle), can't be done with a single infinite
@keyframes— needs multi-stage keyframes over a longer duration OR JS-driven with progressively smaller peaks. See <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-iteration-count">MDN animation-iteration-count</a> for staged approaches. - Add horizontal drift by adding
translateXthat increases per cycle —0% translateX(0) → 100% translateX(20px). Simulates ball losing energy laterally like a real bouncing ball. - For Tailwind:
animate-[bn11_1.2s_ease-in-out_infinite]+animate-[bn11s_1.2s_ease-in-out_infinite]on the shadow. Register both keyframes. See FAQ #4.
Gotchas — read before shipping
- Do NOT use
topormargin-topto create the vertical bounce — every frame triggers layout+paint. Usetransform: translateY. The squash-and-stretch (scaleY/scaleX) is also transform, so the entire animation stays compositor-safe. - The squash-and-stretch must PRESERVE the ball's visual mass — the horizontal expansion (
scaleX(1.3)) MUST be matched by vertical compression (scaleY(.7)) so the ball's area stays constant. If both grow, the ball looks like it's inflating; if only one changes, it looks like it's stretching from a fixed point. - The ground shadow scale must sync with the ball's height — small shadow at peak (ball far from ground = shadow small and blurred), wide shadow at floor contact (ball touching ground = shadow wide and sharp). Missing this makes the ball look disconnected from its ground.
- For accessibility, this animation is purely DECORATIVE — no information is conveyed. It's fine to run infinitely, but MUST honor
prefers-reduced-motionby disabling entirely. Vestibular-disorder users find continuous ball bouncing nauseating. - <a href="https://www.w3.org/WAI/WCAG21/Understanding/pause-stop-hide.html">WCAG 2.2.2 (Pause, Stop, Hide)</a> — any animation over 5 seconds needs a pause control. This loop is 1.2s per cycle, so no pause control needed under 5s (users could argue the 5s applies to CONTINUOUS motion — safer to add a pause anyway for accessibility).
- The gradient background of the ball should compose WITH the transform, not fight it. Radial gradient on the ball creates depth; make sure the gradient's origin stays at the geometric center of the ball at all keyframe stops.
- For
prefers-reduced-motion, DISABLE the animation entirely — show the ball at rest position (either on floor or at peak). Decorative motion carries no information; reducing motion loses nothing meaningful.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 45+ | 10+ | 40+ | 45+ |
transform + @keyframes. Universal, GPU-accelerated.