@keyframes bounce
transform: rotateY(180deg)
cubic-bezier(.4,.2,.2,1)
animation-delay: .8s

Rehan’s CSS
Animation Lab

Ten hand-built animations, side by side with the exact CSS that makes them work. Open View Source on this page (Cmd+U on Mac, Ctrl+U on Windows) and you already have everything you need to remix any of them. That’s the whole lesson.

Every demo below has two halves: the animation running on the left, and the CSS that does the work on the right. Read the CSS, change a number, refresh, see what happens. That’s how every front-end developer actually learns this stuff.

@keyframes

1. Bouncing ball

Two states (top and bottom) plus cubic-bezier timing functions to fake gravity. The squash at the bottom is what sells it — try setting scaleY to 1 and watch the magic die.

.ball {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #ff8aa8, #ff5e8a 70%, #c0376a);
  animation: bounce 1.4s cubic-bezier(.5,.05,.5,.95) infinite;
}
@keyframes bounce {
  0%, 100% { transform: translateY(-60px); }
  50%      { transform: translateY(60px) scaleY(.9) scaleX(1.1); }
}
infinite rotate

2. Loading spinner

A circle with a transparent border on most sides and a colored border on two. Spin it forever with linear infinite. The cleverness is all in the borders.

.spinner {
  width: 70px; height: 70px;
  border: 6px solid rgba(255,255,255,.1);
  border-top-color: #ff5e8a;
  border-right-color: #fbbf24;
  border-radius: 50%;
  animation: spin 1.1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
Hover me
transition + hover

3. Hover lift & tilt

No @keyframes at all — just a transition property plus a :hover rule. The bouncy feel comes from a cubic-bezier with a value over 1, which lets it overshoot.

.hcard {
  transition: transform .4s cubic-bezier(.2,.9,.3,1.4),
              box-shadow .4s;
}
.hcard:hover {
  transform: translateY(-10px) rotate(-3deg) scale(1.04);
  box-shadow: 0 20px 50px -10px rgba(255,94,138,.6);
}
Hello
World
3D transforms

4. Card flip in 3D

The parent gets perspective, the inner box gets preserve-3d, and each face is rotated by 180deg. backface-visibility: hidden hides the back of each face when it’s facing away.

.flipper        { perspective: 1000px; }
.flipper-inner  { transform-style: preserve-3d;
                   transition: transform .8s; }
.flipper:hover .flipper-inner { transform: rotateY(180deg); }
.face           { backface-visibility: hidden; }
.face.back      { transform: rotateY(180deg); }
Hello, Rehan!
steps() timing

5. Typewriter text

Two animations at once: typing (width grows in steps so it looks like one letter at a time), and blink (the cursor on the right border).

.typer {
  white-space: nowrap;
  overflow: hidden;
  border-right: 3px solid #ff5e8a;
  width: 18ch;
  animation: typing 4s steps(18, end) infinite,
              blink .7s step-end infinite;
}
@keyframes typing { 0%, 100% { width: 0; } 50% { width: 18ch; } }
@keyframes blink  { 50% { border-color: transparent; } }
animation-delay

6. Floating bubbles

Same animation on four elements, with different animation-duration and reverse. That alone makes them feel independent — no JS.

.bubble.b1 { animation: floatBubble 5s ease-in-out infinite; }
.bubble.b2 { animation: floatBubble 6s ease-in-out infinite reverse; }
.bubble.b3 { animation: floatBubble 4s ease-in-out infinite; }
.bubble.b4 { animation: floatBubble 7s ease-in-out infinite reverse; }
@keyframes floatBubble {
  0%, 100% { transform: translate(0,0) scale(1); }
  33%      { transform: translate(15px,-25px) scale(1.05); }
  66%      { transform: translate(-15px,10px) scale(.95); }
}
::after pseudo-element

7. Wavy underline link

A pseudo-element drawn with two diagonal gradients tiled into a zig-zag. scaleX grows it from the left on hover. No SVG.

.wave-link::after {
  content: '';
  position: absolute;
  left: 0; bottom: -4px;
  width: 100%; height: 8px;
  background:
    linear-gradient(45deg, transparent 33%, #ff5e8a 33% 50%, transparent 50%) 0 0 / 12px 8px,
    linear-gradient(-45deg, transparent 33%, #ff5e8a 33% 50%, transparent 50%) 0 0 / 12px 8px;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s;
}
.wave-link:hover::after { transform: scaleX(1); }
animation-delay

8. Pulsing notification dot

Two rings expand and fade with the same animation, but the second one is delay: .8s — so it’s always halfway behind. Real apps use this for “live” badges.

.pulse-ring {
  position: absolute; inset: 0;
  border-radius: 50%;
  background: #ff5e8a;
  animation: pulseRing 1.6s ease-out infinite;
}
.pulse-ring.r2 { animation-delay: .8s; }
@keyframes pulseRing {
  0%   { transform: scale(1); opacity: .7; }
  100% { transform: scale(3); opacity: 0; }
}
@property + conic-gradient

9. Animated rainbow border

Modern CSS at its most show-off-y. @property registers a custom angle so it can be animated — then a conic-gradient spins around the button. The trick to making the inside transparent is the mask-composite.

@property --a { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

.gborder::before {
  content: '';
  position: absolute; inset: 0;
  padding: 3px;
  background: conic-gradient(from var(--a), #ff5e8a, #fbbf24, #6ee7b7, #4f7cff, #ff5e8a);
  -webkit-mask: linear-gradient(#000 0 0) content-box,
                 linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  animation: rotateConic 4s linear infinite;
}
@keyframes rotateConic { to { --a: 360deg; } }
😎
multi-step keyframe

10. Wobble on hover

A keyframe with five stops — rotate left, right, left, right, settle. The decreasing angles (12° → 10° → 6° → 4°) are what make it feel like real damping.

.wobble:hover { animation: wobble .6s ease-in-out; }
@keyframes wobble {
  0%, 100% { transform: rotate(0); }
  20%      { transform: rotate(-12deg); }
  40%      { transform: rotate(10deg); }
  60%      { transform: rotate(-6deg); }
  80%      { transform: rotate(4deg); }
}

Now your turn

Pick any one of these and change a single number. Make the bounce slower. Make the rainbow spin backward. Add a third pulse ring. The fastest way to learn is to break things on purpose.

Right-click anywhere → View Source. The whole page is one HTML file.