30 CSS Shake Animations30 / 30

CSS + JSMIT licensed

CSS Screen Shake Effect Keyframes

Full-screen impact, engineered safely: a detonation stage where the entire scene shakes on multi-axis keyframes with a flash and vignette pulse (shaking a wrapper div, never body), and a rumble lab with a live intensity slider — @property-registered custom properties driving amplitude and rotation so you can dial the earthquake from 0 to 10.

Published

Live Demo
Try it

The code

<div class="shk-30-group">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&family=Pixelify+Sans:wght@500..700&display=swap" rel="stylesheet">
<section class="shk-30a" aria-label="Full stage screen shake on detonation">
  <div class="shk-30a__clip">
    <div class="shk-30a__screen" id="shk-30a-screen">
      <div class="shk-30a__grid" aria-hidden="true"></div>
      <p class="shk-30a__mission">SECTOR 7 · DEMOLITION RANGE</p>
      <p class="shk-30a__big" id="shk-30a-big">ARMED</p>
      <button class="shk-30a__btn" id="shk-30a-btn" type="button">DETONATE</button>
      <p class="shk-30a__note">the wrapper shakes — <b>body never moves</b>, overlays stay outside</p>
    </div>
    <div class="shk-30a__flash" id="shk-30a-flash" aria-hidden="true"></div>
    <div class="shk-30a__vig" id="shk-30a-vig" aria-hidden="true"></div>
  </div>
</section>
<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-30b" aria-label="Continuous screen rumble with intensity slider">
  <div class="shk-30b__clip">
    <div class="shk-30b__world" id="shk-30b-world" style="--amp:0px;--rot:0deg">
      <div class="shk-30b__skyline" aria-hidden="true"><i style="--h:64px"></i><i style="--h:110px"></i><i style="--h:84px"></i><i style="--h:140px"></i><i style="--h:96px"></i><i style="--h:120px"></i><i style="--h:70px"></i></div>
      <p class="shk-30b__label">RICHTER LAB</p>
    </div>
    <div class="shk-30b__panel">
      <label class="shk-30b__row" for="shk-30b-dial"><span>Intensity</span><input class="shk-30b__dial" id="shk-30b-dial" type="range" min="0" max="10" step="1" value="0"><output class="shk-30b__out" id="shk-30b-out" for="shk-30b-dial">0</output></label>
      <code class="shk-30b__code" id="shk-30b-code">--amp: 0px · --rot: 0deg</code>
    </div>
  </div>
</section>
</div>
.shk-30-group {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.shk-30-group > section {
  width: 100%;
}

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

.shk-30a {
  width: 100%;
  min-height: 100vh;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-30a__clip {
  position: relative;
  width: 100%;
  min-height: 100vh;
  overflow: hidden;
  background: #0a0b0f;
}

.shk-30a__screen {
  position: absolute;
  inset: -24px;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  text-align: center;
  background: radial-gradient(110% 110% at 50% 100%,#181b26,#0a0b0f 65%);
  padding: 24px;
}

.shk-30a__screen.is-quaking {
  animation: shk-30a-quake .55s steps(2,jump-none);
}

.shk-30a__grid {
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(120,150,220,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(120,150,220,.07) 1px,transparent 1px);
  background-size: 44px 44px;
  pointer-events: none;
}

.shk-30a__mission {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .3em;
  color: rgba(200,215,245,.5);
}

.shk-30a__big {
  font-family: 'Pixelify Sans',monospace;
  font-size: clamp(44px,9vw,92px);
  letter-spacing: .06em;
  color: #eef2ff;
  text-shadow: 0 0 40px rgba(140,170,255,.35);
}

.shk-30a__big.boom {
  color: oklch(0.78 0.18 55);
  text-shadow: 0 0 50px oklch(0.72 0.19 55 / .7);
}

.shk-30a__btn {
  padding: 16px 40px;
  font-family: 'Pixelify Sans',monospace;
  font-size: 18px;
  letter-spacing: .12em;
  color: #0a0b0f;
  background: oklch(0.72 0.19 55);
  border: 0;
  border-radius: 12px;
  cursor: pointer;
  transition: filter .15s,scale .12s;
}

.shk-30a__btn:hover {
  filter: brightness(1.1);
}

.shk-30a__btn:active {
  scale: .95;
}

.shk-30a__note {
  font-size: 12px;
  color: rgba(200,215,245,.45);
}

.shk-30a__note b {
  color: rgba(230,240,255,.8);
}

.shk-30a__flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.shk-30a__flash.go {
  animation: shk-30a-flash .5s ease-out;
}

.shk-30a__vig {
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 0 transparent;
  pointer-events: none;
}

.shk-30a__vig.go {
  animation: shk-30a-vig .8s ease-out;
}

@keyframes shk-30a-quake {
  0% {
    translate: -14px 8px;
    rotate: -.7deg;
  }

  20% {
    translate: 12px -10px;
    rotate: .6deg;
  }

  40% {
    translate: -10px -6px;
    rotate: -.45deg;
  }

  60% {
    translate: 12px 8px;
    rotate: .5deg;
  }

  80% {
    translate: -6px 4px;
    rotate: -.25deg;
  }

  100% {
    translate: 0 0;
    rotate: 0deg;
  }
}

@keyframes shk-30a-flash {
  0% {
    opacity: .9;
  }

  100% {
    opacity: 0;
  }
}

@keyframes shk-30a-vig {
  0% {
    box-shadow: inset 0 0 120px 40px oklch(0.6 0.21 40 / .55);
  }

  100% {
    box-shadow: inset 0 0 0 0 transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-30a__screen.is-quaking {
    animation: none;
  }

  .shk-30a__flash.go {
    animation: shk-30a-flash 1.2s ease-out;
  }
}

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

@property --shk-30b-amp {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

@property --shk-30b-rot {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}

.shk-30b {
  width: 100%;
  min-height: 100vh;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-30b__clip {
  position: relative;
  width: 100%;
  min-height: 100vh;
  overflow: hidden;
  background: #0b0d13;
  display: grid;
}

.shk-30b__world {
  position: absolute;
  inset: -26px;
  display: grid;
  place-content: center;
  gap: 26px;
  justify-items: center;
  background: linear-gradient(180deg,#10131c 55%,#171c29);
  --shk-30b-amp: var(--amp);
  --shk-30b-rot: var(--rot);
  transition: --shk-30b-amp .4s ease,--shk-30b-rot .4s ease;
  animation: shk-30b-rumble .14s steps(2,jump-none) infinite;
}

.shk-30b__skyline {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

.shk-30b__skyline i {
  width: 34px;
  height: var(--h);
  background: linear-gradient(180deg,#232a3d,#181e2d);
  border: 1px solid rgba(140,165,220,.2);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
  box-shadow: inset 0 8px 0 rgba(140,165,220,.12);
}

.shk-30b__label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .34em;
  color: rgba(205,220,248,.55);
}

.shk-30b__panel {
  position: absolute;
  left: 50%;
  bottom: 36px;
  translate: -50% 0;
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 16px 22px;
  border-radius: 16px;
  background: rgba(10,12,18,.75);
  border: 1px solid rgba(150,175,230,.2);
  backdrop-filter: blur(10px);
}

.shk-30b__row {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13.5px;
  font-weight: 600;
  color: #dde6f8;
}

.shk-30b__dial {
  width: min(240px,50vw);
  accent-color: oklch(0.72 0.17 45);
}

.shk-30b__out {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 14px;
  font-weight: 700;
  color: oklch(0.78 0.15 45);
  min-width: 2ch;
  text-align: right;
}

.shk-30b__code {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 11px;
  color: rgba(205,220,248,.5);
}

@keyframes shk-30b-rumble {
  0% {
    translate: calc(var(--shk-30b-amp) * -1) calc(var(--shk-30b-amp) * .6);
    rotate: calc(var(--shk-30b-rot) * -1);
  }

  100% {
    translate: var(--shk-30b-amp) calc(var(--shk-30b-amp) * -.6);
    rotate: var(--shk-30b-rot);
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-30b__world {
    animation: none;
  }
}
(function(){
  var screen=document.getElementById('shk-30a-screen'); if(!screen) return;
  var btn=document.getElementById('shk-30a-btn'),big=document.getElementById('shk-30a-big'),
      flash=document.getElementById('shk-30a-flash'),vig=document.getElementById('shk-30a-vig');
  screen.addEventListener('animationend',function(){ screen.classList.remove('is-quaking'); });
  flash.addEventListener('animationend',function(){ flash.classList.remove('go'); });
  vig.addEventListener('animationend',function(){ vig.classList.remove('go'); });
  btn.addEventListener('click',function(){
    big.textContent='BOOM'; big.classList.add('boom');
    screen.classList.remove('is-quaking'); flash.classList.remove('go'); vig.classList.remove('go');
    requestAnimationFrame(function(){ requestAnimationFrame(function(){
      screen.classList.add('is-quaking'); flash.classList.add('go'); vig.classList.add('go');
    }); });
    setTimeout(function(){ big.textContent='ARMED'; big.classList.remove('boom'); },1400);
  });
})();

(function(){
  var world=document.getElementById('shk-30b-world'); if(!world) return;
  var dial=document.getElementById('shk-30b-dial'),out=document.getElementById('shk-30b-out'),
      code=document.getElementById('shk-30b-code');
  dial.addEventListener('input',function(){
    var v=parseInt(dial.value,10);
    var amp=(v*1.4).toFixed(1)+'px', rot=(v*.08).toFixed(2)+'deg';
    world.style.setProperty('--amp',amp);
    world.style.setProperty('--rot',rot);
    out.textContent=String(v);
    code.textContent='--amp: '+amp+' · --rot: '+rot;
  });
})();
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 Screen Shake Effect Keyframes
Source: https://codefronts.com/motion/css-shake-animation/css-screen-shake-effect-keyframes/

Full-screen impact, engineered safely: a detonation stage where the entire scene shakes on multi-axis keyframes with a flash and vignette pulse (shaking a wrapper div, never body), and a rumble lab with a live intensity slider — @property-registered custom properties driving amplitude and rotation so you can dial the earthquake from 0 to 10.
## HTML
```html
<div class="shk-30-group">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400..700&family=Pixelify+Sans:wght@500..700&display=swap" rel="stylesheet">
<section class="shk-30a" aria-label="Full stage screen shake on detonation">
  <div class="shk-30a__clip">
    <div class="shk-30a__screen" id="shk-30a-screen">
      <div class="shk-30a__grid" aria-hidden="true"></div>
      <p class="shk-30a__mission">SECTOR 7 · DEMOLITION RANGE</p>
      <p class="shk-30a__big" id="shk-30a-big">ARMED</p>
      <button class="shk-30a__btn" id="shk-30a-btn" type="button">DETONATE</button>
      <p class="shk-30a__note">the wrapper shakes — <b>body never moves</b>, overlays stay outside</p>
    </div>
    <div class="shk-30a__flash" id="shk-30a-flash" aria-hidden="true"></div>
    <div class="shk-30a__vig" id="shk-30a-vig" aria-hidden="true"></div>
  </div>
</section>
<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-30b" aria-label="Continuous screen rumble with intensity slider">
  <div class="shk-30b__clip">
    <div class="shk-30b__world" id="shk-30b-world" style="--amp:0px;--rot:0deg">
      <div class="shk-30b__skyline" aria-hidden="true"><i style="--h:64px"></i><i style="--h:110px"></i><i style="--h:84px"></i><i style="--h:140px"></i><i style="--h:96px"></i><i style="--h:120px"></i><i style="--h:70px"></i></div>
      <p class="shk-30b__label">RICHTER LAB</p>
    </div>
    <div class="shk-30b__panel">
      <label class="shk-30b__row" for="shk-30b-dial"><span>Intensity</span><input class="shk-30b__dial" id="shk-30b-dial" type="range" min="0" max="10" step="1" value="0"><output class="shk-30b__out" id="shk-30b-out" for="shk-30b-dial">0</output></label>
      <code class="shk-30b__code" id="shk-30b-code">--amp: 0px · --rot: 0deg</code>
    </div>
  </div>
</section>
</div>
```
## CSS
```css
.shk-30-group {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.shk-30-group > section {
  width: 100%;
}

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

.shk-30a {
  width: 100%;
  min-height: 100vh;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-30a__clip {
  position: relative;
  width: 100%;
  min-height: 100vh;
  overflow: hidden;
  background: #0a0b0f;
}

.shk-30a__screen {
  position: absolute;
  inset: -24px;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  text-align: center;
  background: radial-gradient(110% 110% at 50% 100%,#181b26,#0a0b0f 65%);
  padding: 24px;
}

.shk-30a__screen.is-quaking {
  animation: shk-30a-quake .55s steps(2,jump-none);
}

.shk-30a__grid {
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(120,150,220,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(120,150,220,.07) 1px,transparent 1px);
  background-size: 44px 44px;
  pointer-events: none;
}

.shk-30a__mission {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .3em;
  color: rgba(200,215,245,.5);
}

.shk-30a__big {
  font-family: 'Pixelify Sans',monospace;
  font-size: clamp(44px,9vw,92px);
  letter-spacing: .06em;
  color: #eef2ff;
  text-shadow: 0 0 40px rgba(140,170,255,.35);
}

.shk-30a__big.boom {
  color: oklch(0.78 0.18 55);
  text-shadow: 0 0 50px oklch(0.72 0.19 55 / .7);
}

.shk-30a__btn {
  padding: 16px 40px;
  font-family: 'Pixelify Sans',monospace;
  font-size: 18px;
  letter-spacing: .12em;
  color: #0a0b0f;
  background: oklch(0.72 0.19 55);
  border: 0;
  border-radius: 12px;
  cursor: pointer;
  transition: filter .15s,scale .12s;
}

.shk-30a__btn:hover {
  filter: brightness(1.1);
}

.shk-30a__btn:active {
  scale: .95;
}

.shk-30a__note {
  font-size: 12px;
  color: rgba(200,215,245,.45);
}

.shk-30a__note b {
  color: rgba(230,240,255,.8);
}

.shk-30a__flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.shk-30a__flash.go {
  animation: shk-30a-flash .5s ease-out;
}

.shk-30a__vig {
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 0 transparent;
  pointer-events: none;
}

.shk-30a__vig.go {
  animation: shk-30a-vig .8s ease-out;
}

@keyframes shk-30a-quake {
  0% {
    translate: -14px 8px;
    rotate: -.7deg;
  }

  20% {
    translate: 12px -10px;
    rotate: .6deg;
  }

  40% {
    translate: -10px -6px;
    rotate: -.45deg;
  }

  60% {
    translate: 12px 8px;
    rotate: .5deg;
  }

  80% {
    translate: -6px 4px;
    rotate: -.25deg;
  }

  100% {
    translate: 0 0;
    rotate: 0deg;
  }
}

@keyframes shk-30a-flash {
  0% {
    opacity: .9;
  }

  100% {
    opacity: 0;
  }
}

@keyframes shk-30a-vig {
  0% {
    box-shadow: inset 0 0 120px 40px oklch(0.6 0.21 40 / .55);
  }

  100% {
    box-shadow: inset 0 0 0 0 transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-30a__screen.is-quaking {
    animation: none;
  }

  .shk-30a__flash.go {
    animation: shk-30a-flash 1.2s ease-out;
  }
}

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

@property --shk-30b-amp {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

@property --shk-30b-rot {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}

.shk-30b {
  width: 100%;
  min-height: 100vh;
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.shk-30b__clip {
  position: relative;
  width: 100%;
  min-height: 100vh;
  overflow: hidden;
  background: #0b0d13;
  display: grid;
}

.shk-30b__world {
  position: absolute;
  inset: -26px;
  display: grid;
  place-content: center;
  gap: 26px;
  justify-items: center;
  background: linear-gradient(180deg,#10131c 55%,#171c29);
  --shk-30b-amp: var(--amp);
  --shk-30b-rot: var(--rot);
  transition: --shk-30b-amp .4s ease,--shk-30b-rot .4s ease;
  animation: shk-30b-rumble .14s steps(2,jump-none) infinite;
}

.shk-30b__skyline {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

.shk-30b__skyline i {
  width: 34px;
  height: var(--h);
  background: linear-gradient(180deg,#232a3d,#181e2d);
  border: 1px solid rgba(140,165,220,.2);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
  box-shadow: inset 0 8px 0 rgba(140,165,220,.12);
}

.shk-30b__label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .34em;
  color: rgba(205,220,248,.55);
}

.shk-30b__panel {
  position: absolute;
  left: 50%;
  bottom: 36px;
  translate: -50% 0;
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 16px 22px;
  border-radius: 16px;
  background: rgba(10,12,18,.75);
  border: 1px solid rgba(150,175,230,.2);
  backdrop-filter: blur(10px);
}

.shk-30b__row {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13.5px;
  font-weight: 600;
  color: #dde6f8;
}

.shk-30b__dial {
  width: min(240px,50vw);
  accent-color: oklch(0.72 0.17 45);
}

.shk-30b__out {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 14px;
  font-weight: 700;
  color: oklch(0.78 0.15 45);
  min-width: 2ch;
  text-align: right;
}

.shk-30b__code {
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 11px;
  color: rgba(205,220,248,.5);
}

@keyframes shk-30b-rumble {
  0% {
    translate: calc(var(--shk-30b-amp) * -1) calc(var(--shk-30b-amp) * .6);
    rotate: calc(var(--shk-30b-rot) * -1);
  }

  100% {
    translate: var(--shk-30b-amp) calc(var(--shk-30b-amp) * -.6);
    rotate: var(--shk-30b-rot);
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-30b__world {
    animation: none;
  }
}
```

## JavaScript
```js
(function(){
  var screen=document.getElementById('shk-30a-screen'); if(!screen) return;
  var btn=document.getElementById('shk-30a-btn'),big=document.getElementById('shk-30a-big'),
      flash=document.getElementById('shk-30a-flash'),vig=document.getElementById('shk-30a-vig');
  screen.addEventListener('animationend',function(){ screen.classList.remove('is-quaking'); });
  flash.addEventListener('animationend',function(){ flash.classList.remove('go'); });
  vig.addEventListener('animationend',function(){ vig.classList.remove('go'); });
  btn.addEventListener('click',function(){
    big.textContent='BOOM'; big.classList.add('boom');
    screen.classList.remove('is-quaking'); flash.classList.remove('go'); vig.classList.remove('go');
    requestAnimationFrame(function(){ requestAnimationFrame(function(){
      screen.classList.add('is-quaking'); flash.classList.add('go'); vig.classList.add('go');
    }); });
    setTimeout(function(){ big.textContent='ARMED'; big.classList.remove('boom'); },1400);
  });
})();

(function(){
  var world=document.getElementById('shk-30b-world'); if(!world) return;
  var dial=document.getElementById('shk-30b-dial'),out=document.getElementById('shk-30b-out'),
      code=document.getElementById('shk-30b-code');
  dial.addEventListener('input',function(){
    var v=parseInt(dial.value,10);
    var amp=(v*1.4).toFixed(1)+'px', rot=(v*.08).toFixed(2)+'deg';
    world.style.setProperty('--amp',amp);
    world.style.setProperty('--rot',rot);
    out.textContent=String(v);
    code.textContent='--amp: '+amp+' · --rot: '+rot;
  });
})();
```

How this works

The iron rule: never animate body or html — you'll shake scrollbars, fixed elements and browser UI assumptions. Wrap the world:

<div class="screen"> …entire scene… </div>

.screen.is-quaking{animation:quake .5s steps(2,jump-none)}
@keyframes quake{
  0%{translate:-14px 8px;rotate:-.7deg}
  20%{translate:12px -10px;rotate:.6deg}
  40%{translate:-10px -6px;rotate:-.45deg}
  60%{translate:12px 8px;rotate:.5deg}
  80%{translate:-6px 4px;rotate:-.25deg}
  100%{translate:0 0;rotate:0deg}
}

Both axes plus a whisper of rotation, irregular offsets, and overflow:hidden on the stage (a translated wrapper exposes its edges — hide them or oversize the wrapper by the max amplitude). The rumble lab registers --amp with @property so the slider's value interpolates smoothly into the running animation instead of jumping.

Make it yours

  • Big impacts: 12–16px for 500ms; distant rumbles: 3–5px for 1200ms with ease-out decay.
  • Compose flash (60ms), shake (500ms) and vignette pulse (700ms) from one class for a cinematic hit.
  • Oversize the shaking wrapper by inset:-20px and hide overflow on its parent — no exposed edges at any amplitude.
  • For continuous rumble, loop symmetric frames and drive violence ONLY through --amp so intensity is one number.

Gotchas — read before shipping

  • position:fixed children inside a transformed wrapper become position:absolute-like (the transform creates a containing block) — mount overlays outside the shaking div.
  • Screen shake + scroll = motion sickness; only fire while the viewport is stationary and the user acted.
  • steps(2) on a full-viewport layer is cheap; eased full-viewport shake forces giant compositor updates every frame — prefer hard steps.
  • @property needs registration for smooth --amp interpolation; unregistered custom properties snap between values.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Floor set by oklch(), backdrop-filter, @property as this demo is written. The core technique itself goes back further — Chrome 85+.

The shake itself is baseline everywhere. Smooth slider-to-animation interpolation uses @property (Chrome 85 / Safari 16.4 / Firefox 128); without it the rumble still works, intensity changes just snap.

Techniques used in this demo

Search CodeFronts

Loading…