16 CSS Bounce Animations11 / 16

Pure CSSMIT licensed

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

Live Demo
Try it

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>
.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;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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.15 is subtle (realistic rubber), .5 / 1.5 is 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 translateX that 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 top or margin-top to create the vertical bounce — every frame triggers layout+paint. Use transform: 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-motion by 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

ChromeSafariFirefoxEdge
45+10+40+45+

transform + @keyframes. Universal, GPU-accelerated.

Techniques used in this demo

Search CodeFronts

Loading…