Entrances

Bounce In Animation

An element that drops in and bounces to a stop — in pure CSS.

Edit this in the studio

Opens with this animation loaded. Nothing is uploaded — the scene travels in the link.

animation.css
.bounce-in {
  transform: translate3d(180px, 100px, 0);
  width: 120px;
  height: 120px;
  background: #6366f1;
  border-radius: 20px;
  animation: bounce-in-anim 900ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes bounce-in-anim {
  0% {
    transform: translate3d(180px, -60px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  2.78% {
    transform: translate3d(180px, -59.066px, 0);
    opacity: 0.149;
  }
  5.56% {
    transform: translate3d(180px, -56.265px, 0);
    opacity: 0.287;
  }
  8.33% {
    transform: translate3d(180px, -51.597px, 0);
    opacity: 0.416;
  }
  11.11% {
    transform: translate3d(180px, -45.062px, 0);
    opacity: 0.535;
  }
  13.89% {
    transform: translate3d(180px, -36.659px, 0);
    opacity: 0.644;
  }
  16.67% {
    transform: translate3d(180px, -26.389px, 0);
    opacity: 0.742;
  }
  19.44% {
    transform: translate3d(180px, -14.252px, 0);
    opacity: 0.828;
  }
  22.22% {
    transform: translate3d(180px, -0.247px, 0);
    opacity: 0.9;
  }
  25% {
    transform: translate3d(180px, 15.625px, 0);
    opacity: 0.956;
  }
  27.78% {
    transform: translate3d(180px, 33.364px, 0);
    opacity: 0.99;
  }
  30% {
    transform: translate3d(180px, 48.9px, 0);
    opacity: 1;
  }
  30.56% {
    transform: translate3d(180px, 52.971px, 0);
    opacity: 1;
  }
  33.33% {
    transform: translate3d(180px, 74.444px, 0);
    opacity: 1;
  }
  36.11% {
    transform: translate3d(180px, 97.785px, 0);
    opacity: 1;
  }
  38.89% {
    transform: translate3d(180px, 89.66px, 0);
    opacity: 1;
  }
  41.67% {
    transform: translate3d(180px, 80.069px, 0);
    opacity: 1;
  }
  44.44% {
    transform: translate3d(180px, 72.346px, 0);
    opacity: 1;
  }
  47.22% {
    transform: translate3d(180px, 66.489px, 0);
    opacity: 1;
  }
  50% {
    transform: translate3d(180px, 62.5px, 0);
    opacity: 1;
  }
  52.78% {
    transform: translate3d(180px, 60.378px, 0);
    opacity: 1;
  }
  55.56% {
    transform: translate3d(180px, 60.123px, 0);
    opacity: 1;
  }
  58.33% {
    transform: translate3d(180px, 61.736px, 0);
    opacity: 1;
  }
  61.11% {
    transform: translate3d(180px, 65.216px, 0);
    opacity: 1;
  }
  63.89% {
    transform: translate3d(180px, 70.563px, 0);
    opacity: 1;
  }
  66.67% {
    transform: translate3d(180px, 77.778px, 0);
    opacity: 1;
  }
  69.44% {
    transform: translate3d(180px, 86.86px, 0);
    opacity: 1;
  }
  72.22% {
    transform: translate3d(180px, 97.809px, 0);
    opacity: 1;
  }
  75% {
    transform: translate3d(180px, 95.625px, 0);
    opacity: 1;
  }
  77.78% {
    transform: translate3d(180px, 91.975px, 0);
    opacity: 1;
  }
  80.56% {
    transform: translate3d(180px, 90.193px, 0);
    opacity: 1;
  }
  83.33% {
    transform: translate3d(180px, 90.278px, 0);
    opacity: 1;
  }
  86.11% {
    transform: translate3d(180px, 92.23px, 0);
    opacity: 1;
  }
  88.89% {
    transform: translate3d(180px, 96.049px, 0);
    opacity: 1;
  }
  91.67% {
    transform: translate3d(180px, 99.236px, 0);
    opacity: 1;
  }
  94.44% {
    transform: translate3d(180px, 97.623px, 0);
    opacity: 1;
  }
  97.22% {
    transform: translate3d(180px, 97.878px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(180px, 100px, 0);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bounce-in {
    animation: none;
    transition: none;
  }
}

Why it is built this way

CSS timing functions cannot oscillate, so a real bounce cannot be written as a single cubic-bezier. The curve here is sampled and baked into extra keyframes, which is why the CSS has more stops than you would write by hand — and why it needs no JavaScript.

What it animates

2 properties across 2 CSS declarations. The bar shows when each one is moving.

Opacity
opacity
0 1
3 stopscompositor
Y
transform
-160 0px
baked2 stopscompositor

What it costs to run

Opacity and y are handled by the compositor: no layout, no repaint, and the work can run off the main thread. This is the cheap end of CSS animation, which is why it stays smooth on a slow phone where other properties will not.

It runs for 0.90s across 5 keyframe stops, and most of those are not hand-written. The easing here oscillates, and no single cubic-bezier can — so the curve is sampled into extra stops on export. That is why the CSS is longer than you would write yourself, and why it needs no JavaScript.

3 curves in play — Ease Out, Linear and Bounce Out on a plain box. Different properties easing differently is how a movement gets texture: the eye reads the mismatch as weight rather than as a mistake.

Accessibility

Starting at opacity 0 makes the reduced-motion guard load-bearing rather than polite: strip it and a reader who asked for less movement gets no content at all, because nothing ever fades it in. It fades over 0.90s alongside y, and the guard skips all of it together. The generated CSS ends on the resting state, so skipping straight there is safe.

Related animations

bounceentrancephysicskeyframes