30 CSS Shake Animations27 / 30

CSS + JSMIT licensed

CSS Elastic Shake Effect

Elasticity two ways: a card you 'pull and release' whose snap-back rides the modern linear() spring curve (one keyframe pair, the easing does ALL the physics), and a rubber-band pill that stretches on hover and twangs back with chained overshoot keyframes for browsers where you want full control of every bounce.

Published

Live Demo
Try it

The code

<div class="shk-27-group">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
<section class="shk-27a" aria-label="Card that springs back with linear easing">
  <div class="shk-27a__stage">
    <div class="shk-27a__rail" aria-hidden="true"><span class="shk-27a__anchor"></span></div>
    <button class="shk-27a__card" id="shk-27a-card" type="button">
      <span class="shk-27a__label" id="shk-27a-label">Click to pull</span>
      <code class="shk-27a__curve">linear(0, .38, .78, 1.18, 1.28, 1.06, .94, 1.02, .99, 1)</code>
    </button>
    <p class="shk-27a__hint" aria-hidden="true">the keyframes are just 56px → 0 — every bounce lives in the easing function</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500..800&display=swap" rel="stylesheet">
<section class="shk-27b" aria-label="Pill that stretches and twangs back">
  <div class="shk-27b__stage">
    <button class="shk-27b__band" type="button">stretch me</button>
    <p class="shk-27b__hint" aria-hidden="true">hover: slow scaleX pull · leave: 5-stage overshoot chain (1.25 → .88 → 1.08 → .96 → 1)</p>
  </div>
</section>
</div>
.shk-27-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.shk-27-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.shk-27a,
.shk-27a *,
.shk-27a *::before,
.shk-27a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-27a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(170deg,#101620,#0a0e15);
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-27a__stage {
  display: grid;
  justify-items: center;
  gap: 22px;
}

.shk-27a__rail {
  width: 280px;
  height: 2px;
  background: rgba(150,180,230,.2);
  position: relative;
}

.shk-27a__anchor {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 10px;
  translate: -50% -50%;
  border-radius: 50%;
  background: oklch(0.8 0.14 210);
}

.shk-27a__card {
  display: grid;
  gap: 12px;
  padding: 26px 30px;
  border-radius: 18px;
  border: 1px solid oklch(0.8 0.14 210 / .35);
  background: linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.03));
  color: #e6f0fc;
  cursor: pointer;
  transition: translate .35s cubic-bezier(.3,1.2,.5,1);
}

.shk-27a__card.is-pulled {
  translate: 56px 0;
}

.shk-27a__card.is-released {
  animation: shk-27a-snap .9s cubic-bezier(.22,1.6,.36,1) both;
}

@supports (animation-timing-function: linear(0,1)) {
  .shk-27a__card.is-released {
    animation: shk-27a-snap .9s linear(0,.38 6.5%,.78 12.5%,1.18 19%,1.28 22.5%,1.06 30%,.94 36%,1.02 46%,.99 56%,1) both;
  }
}

.shk-27a__label {
  font-size: 17px;
  font-weight: 700;
}

.shk-27a__curve {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 10.5px;
  color: oklch(0.8 0.14 210 / .8);
}

.shk-27a__hint {
  font-size: 12px;
  color: rgba(230,240,252,.4);
  max-width: 420px;
  text-align: center;
}

@keyframes shk-27a-snap {
  from {
    translate: 56px 0;
  }

  to {
    translate: 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-27a__card.is-released {
    animation: none;
  }

  .shk-27a__card.is-pulled {
    translate: 0 0;
  }
}

.shk-27b,
.shk-27b *,
.shk-27b *::before,
.shk-27b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-27b {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,#fff8e8,#fbeecb);
  font-family: 'Bricolage Grotesque','Segoe UI',system-ui,sans-serif;
}

.shk-27b__stage {
  text-align: center;
}

.shk-27b__band {
  padding: 20px 52px;
  font: inherit;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -.01em;
  color: #fff8e8;
  background: linear-gradient(180deg,#e2542f,#c23e1e);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 16px 32px -14px rgba(194,62,30,.65);
  animation: shk-27b-twang .8s cubic-bezier(.36,.07,.19,.97) both;
  transition: scale .45s cubic-bezier(.2,.8,.3,1);
}

.shk-27b__band:hover {
  scale: 1.22 .92;
  animation: none;
}

.shk-27b__band:focus-visible {
  outline: 3px solid #c23e1e;
  outline-offset: 5px;
}

.shk-27b__hint {
  margin-top: 26px;
  font-size: 12px;
  color: rgba(90,40,20,.55);
  max-width: 440px;
  margin-inline: auto;
}

@keyframes shk-27b-twang {
  0% {
    scale: 1.25 .9;
  }

  25% {
    scale: .88 1.07;
  }

  45% {
    scale: 1.08 .96;
  }

  65% {
    scale: .96 1.02;
  }

  82% {
    scale: 1.02 .99;
  }

  100% {
    scale: 1 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-27b__band {
    animation: none;
  }

  .shk-27b__band:hover {
    scale: 1;
  }
}
(function(){
  var card=document.getElementById('shk-27a-card'),label=document.getElementById('shk-27a-label'); if(!card) return;
  var pulled=false;
  card.addEventListener('animationend',function(){ card.classList.remove('is-released'); });
  card.addEventListener('click',function(){
    pulled=!pulled;
    if(pulled){ card.classList.remove('is-released'); card.classList.add('is-pulled'); label.textContent='Click to release'; }
    else{ card.classList.remove('is-pulled'); card.classList.add('is-released'); label.textContent='Click to pull'; }
  });
})();

/* No JavaScript — hover stretches via a slow transition; un-hover re-runs the .band's twang animation (it restarts because hover disabled it with animation:none). Each scale pair conserves volume: X up, Y down. */
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 Shake 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: CSS Elastic Shake Effect
Source: https://codefronts.com/motion/css-shake-animation/css-elastic-shake-effect/

Elasticity two ways: a card you 'pull and release' whose snap-back rides the modern linear() spring curve (one keyframe pair, the easing does ALL the physics), and a rubber-band pill that stretches on hover and twangs back with chained overshoot keyframes for browsers where you want full control of every bounce.
## HTML
```html
<div class="shk-27-group">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
<section class="shk-27a" aria-label="Card that springs back with linear easing">
  <div class="shk-27a__stage">
    <div class="shk-27a__rail" aria-hidden="true"><span class="shk-27a__anchor"></span></div>
    <button class="shk-27a__card" id="shk-27a-card" type="button">
      <span class="shk-27a__label" id="shk-27a-label">Click to pull</span>
      <code class="shk-27a__curve">linear(0, .38, .78, 1.18, 1.28, 1.06, .94, 1.02, .99, 1)</code>
    </button>
    <p class="shk-27a__hint" aria-hidden="true">the keyframes are just 56px → 0 — every bounce lives in the easing function</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500..800&display=swap" rel="stylesheet">
<section class="shk-27b" aria-label="Pill that stretches and twangs back">
  <div class="shk-27b__stage">
    <button class="shk-27b__band" type="button">stretch me</button>
    <p class="shk-27b__hint" aria-hidden="true">hover: slow scaleX pull · leave: 5-stage overshoot chain (1.25 → .88 → 1.08 → .96 → 1)</p>
  </div>
</section>
</div>
```
## CSS
```css
.shk-27-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.shk-27-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.shk-27a,
.shk-27a *,
.shk-27a *::before,
.shk-27a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-27a {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(170deg,#101620,#0a0e15);
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-27a__stage {
  display: grid;
  justify-items: center;
  gap: 22px;
}

.shk-27a__rail {
  width: 280px;
  height: 2px;
  background: rgba(150,180,230,.2);
  position: relative;
}

.shk-27a__anchor {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 10px;
  translate: -50% -50%;
  border-radius: 50%;
  background: oklch(0.8 0.14 210);
}

.shk-27a__card {
  display: grid;
  gap: 12px;
  padding: 26px 30px;
  border-radius: 18px;
  border: 1px solid oklch(0.8 0.14 210 / .35);
  background: linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.03));
  color: #e6f0fc;
  cursor: pointer;
  transition: translate .35s cubic-bezier(.3,1.2,.5,1);
}

.shk-27a__card.is-pulled {
  translate: 56px 0;
}

.shk-27a__card.is-released {
  animation: shk-27a-snap .9s cubic-bezier(.22,1.6,.36,1) both;
}

@supports (animation-timing-function: linear(0,1)) {
  .shk-27a__card.is-released {
    animation: shk-27a-snap .9s linear(0,.38 6.5%,.78 12.5%,1.18 19%,1.28 22.5%,1.06 30%,.94 36%,1.02 46%,.99 56%,1) both;
  }
}

.shk-27a__label {
  font-size: 17px;
  font-weight: 700;
}

.shk-27a__curve {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 10.5px;
  color: oklch(0.8 0.14 210 / .8);
}

.shk-27a__hint {
  font-size: 12px;
  color: rgba(230,240,252,.4);
  max-width: 420px;
  text-align: center;
}

@keyframes shk-27a-snap {
  from {
    translate: 56px 0;
  }

  to {
    translate: 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-27a__card.is-released {
    animation: none;
  }

  .shk-27a__card.is-pulled {
    translate: 0 0;
  }
}

.shk-27b,
.shk-27b *,
.shk-27b *::before,
.shk-27b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-27b {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,#fff8e8,#fbeecb);
  font-family: 'Bricolage Grotesque','Segoe UI',system-ui,sans-serif;
}

.shk-27b__stage {
  text-align: center;
}

.shk-27b__band {
  padding: 20px 52px;
  font: inherit;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -.01em;
  color: #fff8e8;
  background: linear-gradient(180deg,#e2542f,#c23e1e);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 16px 32px -14px rgba(194,62,30,.65);
  animation: shk-27b-twang .8s cubic-bezier(.36,.07,.19,.97) both;
  transition: scale .45s cubic-bezier(.2,.8,.3,1);
}

.shk-27b__band:hover {
  scale: 1.22 .92;
  animation: none;
}

.shk-27b__band:focus-visible {
  outline: 3px solid #c23e1e;
  outline-offset: 5px;
}

.shk-27b__hint {
  margin-top: 26px;
  font-size: 12px;
  color: rgba(90,40,20,.55);
  max-width: 440px;
  margin-inline: auto;
}

@keyframes shk-27b-twang {
  0% {
    scale: 1.25 .9;
  }

  25% {
    scale: .88 1.07;
  }

  45% {
    scale: 1.08 .96;
  }

  65% {
    scale: .96 1.02;
  }

  82% {
    scale: 1.02 .99;
  }

  100% {
    scale: 1 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-27b__band {
    animation: none;
  }

  .shk-27b__band:hover {
    scale: 1;
  }
}
```

## JavaScript
```js
(function(){
  var card=document.getElementById('shk-27a-card'),label=document.getElementById('shk-27a-label'); if(!card) return;
  var pulled=false;
  card.addEventListener('animationend',function(){ card.classList.remove('is-released'); });
  card.addEventListener('click',function(){
    pulled=!pulled;
    if(pulled){ card.classList.remove('is-released'); card.classList.add('is-pulled'); label.textContent='Click to release'; }
    else{ card.classList.remove('is-pulled'); card.classList.add('is-released'); label.textContent='Click to pull'; }
  });
})();

/* No JavaScript — hover stretches via a slow transition; un-hover re-runs the .band's twang animation (it restarts because hover disabled it with animation:none). Each scale pair conserves volume: X up, Y down. */
```

How this works

Elastic motion used to need 10 hand-tuned keyframes. linear() changes the game — encode a damped spring INTO the timing function and keep the keyframes trivial:

@keyframes snapback{from{translate:56px 0}to{translate:0 0}}
.card.is-released{
  animation:snapback .9s linear(
    0, .38 6.5%, .78 12.5%, 1.18 19%, 1.28 22.5%,
    1.06 30%, .94 36%, 1.02 46%, .99 56%, 1
  ) both;
}

Values above 1 ARE the overshoot — the element flies past its target and oscillates in, all from a linear A→B keyframe. The rubber-band variant does it the classical way (scaleX chain 1.25 → .88 → 1.08 → .96 → 1) which still wins when you need per-bounce control or pre-2023 browser support. Same physics, two eras of CSS.

Make it yours

  • More bounce: raise the first overshoot (1.28 → 1.4) and add one more crossing pair.
  • Stiffer spring: compress the percentages (peak at 15% instead of 22.5%).
  • Generate precise curves from real spring params (mass/stiffness/damping) with any linear()-generator tool, then paste.
  • Rubber-band: keep total stretch ≤1.3× and anchor transform-origin at the pulled edge.

Gotchas — read before shipping

  • linear() needs Chrome 113 / Safari 17.2 / Firefox 112 — wrap in @supports and fall back to a cubic-bezier snap (this demo does).
  • cubic-bezier() can only overshoot with y-values outside 0–1 and only ONCE per curve — multi-bounce needs linear() or keyframe chains.
  • Overshoot + overflow:hidden parents = clipped bounces; give elastic elements room.
  • Don't ease-elastic color or opacity — overshooting past pure white/black clamps and bands.

Browser support

ChromeSafariFirefoxEdge
113+17.2+112+113+

linear() is the modern path (Chrome 113 / Safari 17.2 / Firefox 112) and is @supports-gated here with a bezier fallback. The rubber-band variant runs everywhere.

Techniques used in this demo

Search CodeFronts

Loading…