16 CSS Bounce Animations15 / 16

Pure CSSMIT licensed

DVD Screensaver Viewport Bounce

The cult DVD screensaver bounce — a logo travels diagonally across the viewport and cleanly bounces off all four edges, hoping to land perfectly in a corner (the internet's favorite unsolved problem). Retro dark surface with an 80s neon-purple logo. Pure CSS with separate X and Y keyframes at coprime durations so the trajectory never visually repeats. Zero JavaScript. Every 90s kid searches for this at some point.

Published

Live Demo
Try it

The code

<div class="bn-15">
  <p class="bn-15__label">DVD screensaver viewport bounce</p>
  <div class="bn-15__viewport" aria-hidden="true">
    <div class="bn-15__logo">DVD</div>
  </div>
  <p class="bn-15__hint">X + Y keyframes at coprime durations — trajectory never visually repeats.</p>
</div>
.bn-15 {
  --page-bg: #050510;
  --viewport-bg: #0a0a18;
  --ink: #f5f5fa;
  --sub: #8b8ba0;
  --neon-a: #a855f7;
  --neon-b: #22d3ee;
  --line: #1a1a30;
  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: 20px;
  padding: 32px;
  background: radial-gradient(80% 60% at 50% 50%, rgba(168,85,247,.08) 0%, transparent 60%), var(--page-bg);
  color: var(--ink);
  text-align: center;
}

.bn-15 *,
.bn-15 *::before,
.bn-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-15 ::selection {
  background: var(--neon-a);
  color: #050510;
}

.bn-15__label {
  font-size: .9rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.bn-15__viewport {
  position: relative;
  width: min(600px,100%);
  aspect-ratio: 16/9;
  background: var(--viewport-bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 20px 40px -12px rgba(168,85,247,.14),inset 0 0 60px rgba(168,85,247,.08);
}

.bn-15__logo {
  position: absolute;
  top: 0;
  left: 0;
  padding: 10px 22px;
  border-radius: 10px;
  background: linear-gradient(135deg,var(--neon-a),var(--neon-b));
  color: #fff;
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 1.1rem;
  font-weight: 900;
  letter-spacing: .08em;
  box-shadow: 0 0 20px rgba(168,85,247,.5),0 0 40px rgba(34,211,238,.35);
  animation: bn-15-x 4.7s linear infinite alternate, bn-15-y 3.3s linear infinite alternate;
  will-change: transform;
}

@keyframes bn-15-x {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(calc(600px - 100%));
  }
}

@keyframes bn-15-y {
  0% {
    transform: translateY(0);
  }

  100% {
    transform: translateY(calc(340px - 100%));
  }
}
/* Combine both transforms via the CSS animation composition — modern browsers stack */

@supports (animation-composition: add) {
  .bn-15__logo {
    animation-composition: add;
  }
}
/* Fallback for browsers without composition: single combined keyframe (won't produce independent X/Y coprime bounce but stays visually acceptable) */

@supports not (animation-composition: add) {
  .bn-15__logo {
    animation: bn-15-combined 4.7s linear infinite alternate;
  }

  @keyframes bn-15-combined {
    0% {
      transform: translate(0,0);
    }

    25% {
      transform: translate(calc(600px - 100%),40px);
    }

    50% {
      transform: translate(calc(600px - 100%),calc(340px - 100%));
    }

    75% {
      transform: translate(0,calc(340px - 100%));
    }

    100% {
      transform: translate(0,0);
    }
  }
}

.bn-15__hint {
  font-size: .78rem;
  color: var(--sub);
  max-width: 400px;
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .bn-15__logo {
    animation: none!important;
    transform: translate(calc((600px - 100%) / 2),calc((340px - 100%) / 2))!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: DVD Screensaver Viewport Bounce
Source: https://codefronts.com/motion/css-bounce-animation/dvd-screensaver-viewport-bounce/

The cult DVD screensaver bounce — a logo travels diagonally across the viewport and cleanly bounces off all four edges, hoping to land perfectly in a corner (the internet's favorite unsolved problem). Retro dark surface with an 80s neon-purple logo. Pure CSS with separate X and Y keyframes at coprime durations so the trajectory never visually repeats. Zero JavaScript. Every 90s kid searches for this at some point.
## HTML
```html
<div class="bn-15">
  <p class="bn-15__label">DVD screensaver viewport bounce</p>
  <div class="bn-15__viewport" aria-hidden="true">
    <div class="bn-15__logo">DVD</div>
  </div>
  <p class="bn-15__hint">X + Y keyframes at coprime durations — trajectory never visually repeats.</p>
</div>
```
## CSS
```css
.bn-15 {
  --page-bg: #050510;
  --viewport-bg: #0a0a18;
  --ink: #f5f5fa;
  --sub: #8b8ba0;
  --neon-a: #a855f7;
  --neon-b: #22d3ee;
  --line: #1a1a30;
  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: 20px;
  padding: 32px;
  background: radial-gradient(80% 60% at 50% 50%, rgba(168,85,247,.08) 0%, transparent 60%), var(--page-bg);
  color: var(--ink);
  text-align: center;
}

.bn-15 *,
.bn-15 *::before,
.bn-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-15 ::selection {
  background: var(--neon-a);
  color: #050510;
}

.bn-15__label {
  font-size: .9rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.bn-15__viewport {
  position: relative;
  width: min(600px,100%);
  aspect-ratio: 16/9;
  background: var(--viewport-bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 20px 40px -12px rgba(168,85,247,.14),inset 0 0 60px rgba(168,85,247,.08);
}

.bn-15__logo {
  position: absolute;
  top: 0;
  left: 0;
  padding: 10px 22px;
  border-radius: 10px;
  background: linear-gradient(135deg,var(--neon-a),var(--neon-b));
  color: #fff;
  font-family: 'JetBrains Mono',ui-monospace,monospace;
  font-size: 1.1rem;
  font-weight: 900;
  letter-spacing: .08em;
  box-shadow: 0 0 20px rgba(168,85,247,.5),0 0 40px rgba(34,211,238,.35);
  animation: bn-15-x 4.7s linear infinite alternate, bn-15-y 3.3s linear infinite alternate;
  will-change: transform;
}

@keyframes bn-15-x {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(calc(600px - 100%));
  }
}

@keyframes bn-15-y {
  0% {
    transform: translateY(0);
  }

  100% {
    transform: translateY(calc(340px - 100%));
  }
}
/* Combine both transforms via the CSS animation composition — modern browsers stack */

@supports (animation-composition: add) {
  .bn-15__logo {
    animation-composition: add;
  }
}
/* Fallback for browsers without composition: single combined keyframe (won't produce independent X/Y coprime bounce but stays visually acceptable) */

@supports not (animation-composition: add) {
  .bn-15__logo {
    animation: bn-15-combined 4.7s linear infinite alternate;
  }

  @keyframes bn-15-combined {
    0% {
      transform: translate(0,0);
    }

    25% {
      transform: translate(calc(600px - 100%),40px);
    }

    50% {
      transform: translate(calc(600px - 100%),calc(340px - 100%));
    }

    75% {
      transform: translate(0,calc(340px - 100%));
    }

    100% {
      transform: translate(0,0);
    }
  }
}

.bn-15__hint {
  font-size: .78rem;
  color: var(--sub);
  max-width: 400px;
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .bn-15__logo {
    animation: none!important;
    transform: translate(calc((600px - 100%) / 2),calc((340px - 100%) / 2))!important;
  }
}
```

How this works

Two independent animations run simultaneously on the same logo element: @keyframes bn-15-x handles horizontal travel — 0% translateX(-100%) → 100% translateX(100%) with animation-direction: alternate so it ping-pongs between left and right edges. @keyframes bn-15-y handles vertical travel — same pattern for top and bottom. Both use animation-timing-function: linear (constant velocity — real DVD screensavers don't accelerate) and animation-iteration-count: infinite.

The key trick: X and Y use DIFFERENT durations. If both ran for 4 seconds, the logo would trace a perfect X pattern and return to start every 4 seconds — boring and predictable. With X at 4.7 seconds and Y at 3.3 seconds (coprime — no small common multiple), the trajectory takes many minutes to repeat visually. Users perceive the motion as "random" even though it's deterministic.

On each edge collision, CSS animation-direction: alternate reverses that axis — the logo doesn't bounce with a spring or squash; it snaps 180° at the edge (like the real DVD screensaver, which was just linear ping-pong not physics). The illusion of "bouncing" comes from the direction change, not from any elastic keyframe.

The demo shows a bordered viewport-style container (~600×340px) with a small logo pill inside currently mid-flight. On prefers-reduced-motion, the animation stops — logo rests at its center.

Make it yours

  • Change the logo speed by editing the X and Y durations — faster (2s / 1.3s) reads as more energetic, slower (10s / 7s) reads as more meditative. Keep the two values coprime (no small common multiple).
  • For a truly random-feeling trajectory, use non-coprime but irrational-ratio durations like 4.7s / 3.3s or 5.2s / 3.9s. Common multiples over 100 seconds make the loop visually infinite.
  • For a corner-hit celebration (Reddit's r/DVDScreensaverPointsCorner obsession), add a click/hover-triggered highlight animation when the logo is near a corner — pure CSS can't detect collision, but you can flash the logo periodically to simulate the excitement.
  • For platforms with dynamic viewport sizes, use viewport units (translateX(calc(100vw - 100%))) instead of container-relative percentages. Requires re-thinking the container-relative CSS.
  • For Tailwind: register two keyframes and animation utilities. Use two class names on the logo — animate-[bn15x_4.7s_linear_infinite_alternate] AND animate-[bn15y_3.3s_linear_infinite_alternate]. See FAQ #4.

Gotchas — read before shipping

  • The X and Y keyframes MUST be applied to the SAME element for the diagonal motion to work — split them across parent and child and the transforms compose but centering breaks.
  • The two animation durations MUST be coprime OR irrational-ratio — if both are simple ratios of the same base (e.g., 4s and 8s), the trajectory repeats visually every 8 seconds. Users perceive the repetition instantly.
  • Do NOT animate top or left — every frame triggers layout+paint. Use transform: translate which stays on the compositor thread.
  • For real corner-perfect bounces (the internet's obsession), you'd need to calculate viewport dimensions and use exact translate values — but even then, floating-point rounding usually prevents perfect corner hits. Perfectionism aside, the visual illusion is fine.
  • For accessibility, this animation is PURELY DECORATIVE — no information conveyed. On prefers-reduced-motion, disable entirely. Vestibular-disorder users find continuous diagonal motion nauseating.
  • WCAG 2.2.2 (Pause, Stop, Hide) — any animation over 5 seconds needs a pause mechanism. This runs infinitely, so add a pause button in production. The demo is auto-paused via prefers-reduced-motion, which technically satisfies the rule for users who need it.
  • For nostalgic contexts (404 pages, error pages, empty states), the DVD screensaver bounce works. For serious contexts (checkout, medical forms), it's inappropriate — decorative motion feels frivolous.
  • The logo's colors + font should match the 80s DVD aesthetic (neon purple / cyan on black, bold retro sans) for the visual reference to land. Modern gradient logos on a light background lose the nostalgic resonance.

Browser support

ChromeSafariFirefoxEdge
45+10+40+45+

transform + @keyframes + animation-direction. Universal, GPU-accelerated.

Techniques used in this demo

Search CodeFronts

Loading…