TransitionsFree

Elastic Spring

A 50 px ball that jumps to 60 px, lands squashing (1.15 × 0.85), bounces to 25 then 8 px and settles, in 2 s: a single CSS keyframe whose easing overshoots its target at each step, hence the elasticity — zero JavaScript, a simple pulse when the user reduces motion.

CSSPhysiqueRebond

Updated

Hover or click the scene to interact

This effect is free — the Transitions category contains 52 effects total, including 9 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Thursday, 3:40 pmRoom 12 · Mr Garnier

Léa

your granddaughter is calling

On the call · 00:47

① A call from a loved one — simplified tablet for a nursing home resident

WhenThe granddaughter is calling: the badge with her photo bounces above her name; as soon as the resident taps "Answer", the badge stops and the call timer starts.
WhyThe elastic bounce catches the eye like a visual ringtone, useful when hearing declines; stillness says the call has been picked up, with no other message.
SettingsBall enlarged to 96 px, 4 px white border and a portrait drawn with gradients; original keyframe and duration (2 s, jumps of 60, 25 and 8 px); 52 px tall buttons, 20 px text; state B: animation: none on the badge.

Your turn, Jules!Tap the die to roll it

4! Move 4 squaresThe goose sends you on again

Roll the die
171819202122 goose23

② A die waiting to be rolled — a digital Game of the Goose for children

WhenIt is Jules's turn: the die hops in place above the board; he taps it, the die lands on 4 and the target square gets a dotted outline.
WhySquash and stretch give the die the feel of a rubber toy, and the child understands, without reading, that it is their turn.
Settings72 px element with 16 px corners (border-radius instead of the circle), white face and blue pips; same bounce; on the roll, animation: none and the face goes from 5 to 4.
1 h 02sitting since the 10 am meeting
3 / 5breaks taken today

Time to stand up

3 minutes: shoulders, back, a glass of water

2:47

Roll your shoulders back, 10 times

③ A nudge to stand up — an active-break app for remote work

WhenAfter an hour sitting, the reminder card opens with a green ball bouncing on a floor line; "Start the break" stops it and shows the 3-minute countdown.
WhyA hopping ball makes you want to move, without an alarm or a harsh sound; it stops when the break begins, as if handing over.
Settings62 px apple-green gradient ball above a 3 px line standing for the floor; original bounce; state B: ball still, 30 px timer.

How it works

Three elements: the .md-spring-stage frame (250 px tall, background #1a1a24), the .md-spring area that centers, and .md-spring-ball, a 50 px disc in an indigo to pink gradient (#6366f1 → #ec4899). All the physics lives in @keyframes mdSpring, played in a loop: animation: mdSpring 2s cubic-bezier(0.34, 1.56, 0.64, 1) infinite. The steps alternate jumps and contacts: 20% in the air at −60 px, stretched (0.9 × 1.1); 40% on the ground, squashed (1.15 × 0.85); 55% at −25 px; 70% on the ground (1.05 × 0.95); 85% at −8 px; 100% at rest. Each bounce is roughly 40% of the previous one, and the deformations shrink with it: that is what makes it feel like a material absorbing the impact.

The easing does the rest. Its second value, 1.56, is above 1: between two steps, the ball goes a little past the target before coming back. Measured frame by frame in Chromium 151: top of the first jump at −65.8 px instead of −60, a 5.8 px dip below the resting point at contact, maximum squash 1.17 × 0.83. That overshoot, applied to every segment, acts as the spring; with an ease-in-out curve, the same keyframe would give a ball going up and down without bouncing. The transform origin stays at the center: when squashed, the ball shrinks toward its middle and its bottom rises by about 4 px — see the settings to rest it on a floor. Only transform is animated, with no layout recalculation.

The effect is an attention call that loops, not a transition between two states: it moves as long as something is waiting for an action. It is up to your page to stop it when the action happens (call answered, die rolled): animation: none on the ball puts it back at rest, as in the three examples. The frame has its own class, prefixed like the rest of the effect: two effects from the library pasted on the same page do not overwrite each other.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce, the bounce keyframe is replaced by mdSpringFade, an opacity pulse from 1 to 0.45 on the same 2 s rhythm. Measured in Chromium 151, Firefox 153 and WebKit 26: no more movement or deformation (identical transform at every measurement), changing opacity. The "something is waiting" signal stays readable without motion.
  • Stopping: motion lasting more than 5 seconds must be stoppable. Here, the natural stop is the expected action itself (answer, roll, start): set animation: none as soon as it happens, and do not leave the ball bouncing in a part of the page that is not waiting for anything.
  • Meaning: the ball is an empty div with no role. If it signals a call or a game turn, also write it as text next to it ("Léa is calling"); if it acts as a button, give it role="button", tabindex="0" and a label, or put it inside a real button.
  • Contrast: the indigo-pink gradient stands out at more than 3:1 on the frame's #1a1a24 background; on a light background, check the indigo end, the darker one.

Browser compatibility

Pure CSS (@keyframes, transform, cubic-bezier with a value outside 0-1), 1.4 KB, zero JavaScript. Measured in Chromium 151, Firefox 153 and WebKit 26: same bounce, same replacement pulse under reduced motion. A cubic-bezier curve whose y value exceeds 1 is valid in the spec and read by all three engines.

Chrome 43+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS 9+✓ Full
Android Chrome 43+✓ Full

Without CSS animations, the ball stays still in the center of its frame, round and in color: the element shows, only the bounce is missing.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="md-spring-stage">
  <div class="md-spring">
    <div class="md-spring-ball"></div>
  </div>
</div>
CSS
.md-spring-stage,
.md-spring-stage *,
.md-spring-stage *::before,
.md-spring-stage *::after {
  box-sizing: border-box
}

.md-spring-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.md-spring {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.md-spring-ball {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: linear-gradient(135deg, #6366f1, #ec4899);
  animation: mdSpring 2s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}

@keyframes mdSpring {

  0%,
  100% {
    transform: translateY(0) scaleX(1) scaleY(1);
  }

  20% {
    transform: translateY(-60px) scaleX(0.9) scaleY(1.1);
  }

  40% {
    transform: translateY(0) scaleX(1.15) scaleY(0.85);
  }

  55% {
    transform: translateY(-25px) scaleX(0.95) scaleY(1.05);
  }

  70% {
    transform: translateY(0) scaleX(1.05) scaleY(0.95);
  }

  85% {
    transform: translateY(-8px);
  }
}

/* Mouvement réduit : ni saut ni déformation, une pulsation d'opacité garde le signal */
@media (prefers-reduced-motion: reduce) {
  .md-spring-ball {
    animation: mdSpringFade 2s ease-in-out infinite;
  }
}

@keyframes mdSpringFade {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.45;
  }
}
JavaScript (fx-0726)

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Jump heights (CSS — translateY at 20%, 55% and 85%) −60 / −25 / −8 px Keep a ratio close to 0.4 between two bounces for believable damping: −90 / −36 / −14 px for a bigger ball. Equal jumps give a ball that never loses energy.
Squash and stretch (CSS — scaleX / scaleY) 1.15 × 0.85 at first contact, 0.9 × 1.1 in the air 1.3 × 0.7 gives a very soft material (jelly); 1.05 × 0.95 a tennis ball. Keep the product of the two close to 1 so the ball seems to keep its volume.
Easing (CSS — cubic-bezier(0.34, 1.56, 0.64, 1)) about 10% overshoot 1.56 → 1.9 strengthens the spring (higher top, deeper dip); 1.56 → 1 removes the overshoot and the elastic feel.
Duration (CSS — 2s) 2 s per full cycle 1.4 s makes the ball jumpy; 3 s heavier. For a bounce followed by a real pause, stretch the keyframe: end the steps at 60% and leave the ball at rest until 100%.
Stop (CSS — animation: none) none: infinite loop Set by a state class (e.g. .is-answered .md-spring-ball), it puts the ball back at rest immediately. To finish the current jump, wait for the animationiteration event before adding the class.
Size and color (CSS — .md-spring-ball) 50 px, #6366f1 → #ec4899 gradient Any element can bounce: a photo, an icon, a die. Distances are in pixels, so a 100 px ball keeps 60 px jumps; raise them with the size.

FAQ

How do I stop the bounce when the user acts?

Add a state class on a parent and the rule .is-done .md-spring-ball { animation: none; }: the ball returns to rest at once. If the jump gets cut mid-air and it shows, listen for animationiteration on the ball and add the class at that moment, when it touches the ground at the end of a cycle. To restart, remove the class: the animation begins again from the start.

Why does the ball sink slightly below its starting point?

Because of the cubic-bezier(0.34, 1.56, 0.64, 1) curve: its second value is above 1, so each segment goes past its target before coming back. Measured: 5.8 px below rest at contact, top at 65.8 px instead of 60. That overshoot is what makes it springy. If the ball sits on a floor line and the dip shows, raise the floor by 6 px or lower 1.56 to 1.3.

How do I squash it against the floor rather than around its middle?

Add transform-origin: 50% 100% to .md-spring-ball. By default, squashing happens around the center: at contact, the bottom of the ball rises by about 4 px and it seems to float. With the origin at the bottom, the bottom stays on the floor and only the height flattens, like a real ball. The jumps (translateY) do not change.