← Projects/Tutorials/CSS Animation 101
MyKeyZ Tutorial · Motion

CSS Animation 101

Everything moves, you decide how. Pure CSS, no JavaScript, a real wow effect.

Beginner8 chaptersabout 50 minutesProject: animated profile card

Recommended after CSS 101.

Chapter 01

Transitions — smooth changes

Assembly planSmooth instead of abrupt

transition makes the change between two states smooth.

State A.btn, normal
State B.btn:hover

The order in the rule: property, duration, curve, delay.

  • transition: background 0.3s ease
  • :hover

The easiest way into CSS motion: transition makes the change between two states smooth — a soft glide instead of an abrupt cut.

CSS
/* Without transition: abrupt jump */
.btn {
  background: #333;
}
.btn:hover {
  background: #fa5c7b;
}

/* With transition: smooth change */
.btn {
  background: #333;
  transition: background 0.3s ease;
  /*          ^property  ^duration ^curve */
}
.btn:hover {
  background: #fa5c7b;
}

// Syntax

transition: [property] [duration] [timing-function] [delay]

transition: all 0.3s ease; — animates ALL changeable properties
transition: color 0.2s, transform 0.4s ease-out; — several, separated by commas

What can be animated with a transition?

Almost anything numeric: color, background, opacity, transform, width, height, padding, border-radius, box-shadow…

⚠ Performance

Where possible, animate only transform and opacity — these run on the GPU and are silky smooth. width, height, top, left trigger a layout recalculation = stutter.

Chapter 02

@keyframes — defining animations

Assembly planA choreography in two steps

First use @keyframes to define what happens. Then assign it to an element with animation.

@keyframes myFadewhat happens at 0 %, 50 %, 100 %
CSS fills the gapsinterpolates the rest
.elementanimation: myFade 0.6s ease forwards
  • @keyframes
  • 0% 50% 100%
  • animation

With @keyframes you define an animation choreography — you decide what happens at 0%, 50% and 100%. CSS interpolates the rest automatically.

CSS
/* 1. Define the animation */
@keyframes myFade {
  0%   { opacity: 0; transform: translateY(20px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* 2. Apply the animation */
.element {
  animation: myFade 0.6s ease forwards;
  /*         ^name  ^duration ^curve ^keep end state */
}

Live demos: the basic moves

fade
slide
spin
bounce
scale+rotate
CSS
/* Fade */
@keyframes fade { 0%,100% { opacity:1; } 50% { opacity:0.1; } }

/* Slide */
@keyframes slide { 0%,100% { transform: translateX(-30px); } 50% { transform: translateX(30px); } }

/* Spin */
@keyframes spin { to { transform: rotate(360deg); } }

/* Bounce */
@keyframes bounce {
  0%,100% { transform: translateY(0); }
  40%     { transform: translateY(-40px); }
  60%     { transform: translateY(-20px); }
}

/* Scale + Rotate */
@keyframes scaleRotate {
  0%,100% { transform: scale(1); }
  50%     { transform: scale(1.6) rotate(45deg); }
}

Chapter 03

Animation properties at a glance

Assembly planEight controls, one shorthand

Each animation-… property controls one part. The shorthand combines them.

what and how long
namewhich keyframes
durationone run
timing-functioncurve
when and how often
delaydelay
iteration-countinfinite
directionalternate
before, after, pause
fill-modeforwards
play-statepaused
  • animation: name duration timing delay iteration direction fill-mode
Property What it does Example value
animation-name Which @keyframes to use fadeIn
animation-duration How long one cycle lasts 0.6s / 400ms
animation-timing-function Speed curve ease / linear
animation-delay Delay before starting 0.2s
animation-iteration-count How often to repeat infinite / 3
animation-direction Direction of the repetition normal / alternate
animation-fill-mode State before/after the animation forwards / both
animation-play-state Pause/play via CSS paused / running

// Shorthand

animation: name duration timing delay iteration direction fill-mode;

Example: animation: fadeIn 0.5s ease 0.2s 1 normal forwards;

fill-mode — often forgotten, always important

CSS
/* forwards = element stays in the end state of the animation */
.card-fade-in {
  opacity: 0;
  animation: fadeInUp 0.6s ease forwards;
}
/* Without forwards: the element jumps back to opacity:0 at the end! */

Chapter 04

Timing functions — the soul of animation

Assembly planSame distance, different feel

The timing function decides how a movement feels.

MovementHow should it feel?
Choice
linearmechanical: loading bar, spinner
ease-outarriving: brakes gently
ease-inleaving: speeds up
steps(n)jumps: pixel art, typewriter
  • ease ease-in ease-out ease-in-out
  • cubic-bezier()
  • steps()

The timing function decides how an object feels. Same distance, same time — but a completely different effect:

Value Character Ideal for
linear Constant speed, mechanical Progress bars, spinners
ease Starts slowly, speeds up, slows down General use, default
ease-in Starts slowly, ends fast Things that "leave"
ease-out Starts fast, slows down gently Things that "arrive"
ease-in-out Like ease, but more symmetrical Loops, hover effects
cubic-bezier(x,y,x,y) Full control Bounce, overshoot, custom
steps(n) Jumps instead of transitions Pixel art, typewriter
CSS
/* Bounce effect via cubic-bezier */
animation: slideIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
/*                                   ↑ value > 1 = Overshoot = springy feel */

/* Typewriter via steps */
animation: typing 3s steps(30) infinite;

✅ Tip

cubic-bezier.com — a visual editor for your own curves. Just play around and copy the generated code!

Chapter 05

Transform — move, rotate, scale

Assembly planMove, rotate, scale

transform changes how an element looks without shifting the layout.

translatemove
rotaterotate
scalegrow, shrink

Order matters: translate rotate scale gives a different result from scale rotate translate.

  • translateX translateY
  • rotate scale skewX

transform is the best friend of CSS animations — GPU-accelerated, no layout reflow, silky smooth.

CSS
/* Move */
transform: translateX(50px);
transform: translateY(-20px);
transform: translate(10px, -30px);

/* Scale */
transform: scale(1.5);       /* 150% size */
transform: scaleX(2);        /* horizontal only */

/* Rotate */
transform: rotate(45deg);
transform: rotate(-90deg);

/* Skew */
transform: skewX(15deg);

/* Combine — mind the order! */
transform: translateY(-10px) rotate(5deg) scale(1.05);

⚠ Order matters with transform!

translate rotate scale ≠ scale rotate translate — the operations affect each other. Rule of thumb: translate first, then rotate, then scale.

transform-origin — changing the pivot point

CSS
/* Default: centre */
transform-origin: center;

/* Corner as pivot (like a calendar page) */
transform-origin: top left;

/* Exact position */
transform-origin: 20px 80%;

Chapter 06

Useful animation patterns

Assembly planReusable building blocks

Proven patterns: copy, adapt, combine.

Patterns
Pulseattention
Typewritertext appears
Staggerfade in one after another
Your pagewith your own colours and timings
  • @keyframes pulse-ring
  • steps()
  • animation-delay

Pulse — eye-catching pulsing

pulse ring
Hello, World!
typewriter
stagger
CSS
/* PULSE RING */
@keyframes pulse-ring {
  0%   { box-shadow: 0 0 0 0 rgba(250,92,123,0.5); }
  70%  { box-shadow: 0 0 0 20px rgba(250,92,123,0); }
  100% { box-shadow: 0 0 0 0 rgba(250,92,123,0); }
}
.dot { animation: pulse-ring 1.5s ease-out infinite; }

/* TYPEWRITER */
@keyframes typing { 0%,5% { width:0 } 50%,95% { width: 200px } }
@keyframes blink  { 50% { border-color: transparent; } }
.text {
  overflow: hidden; white-space: nowrap;
  width: 0; border-right: 2px solid currentColor;
  animation: typing 3s steps(20) infinite, blink .7s step-end infinite;
}

/* STAGGER — fade in children one after another */
.list-item { opacity: 0; animation: fadeIn 0.4s ease forwards; }
.list-item:nth-child(1) { animation-delay: 0.1s; }
.list-item:nth-child(2) { animation-delay: 0.2s; }
.list-item:nth-child(3) { animation-delay: 0.3s; }
/* ... or with CSS custom properties for n elements */

Hover lift — the card rises on hover

CSS
.card {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 20px 40px rgba(0,0,0,0.3);
}

Shimmer / skeleton loading

CSS
@keyframes shimmer {
  0%   { background-position: -200% 0; }
  100% { background-position:  200% 0; }
}
.skeleton {
  background: linear-gradient(90deg,
    #1f1f23 25%, #2e2e35 50%, #1f1f23 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 6px;
}

Chapter 07

PROJECT: your animated profile card

Assembly planProject: animated profile card

Five effects in one file, CSS only, no JavaScript.

animated-card.htmlone file
Effects
Fade instagger on load
Avatarpulse ring
Hovercard lifts, button shines
Badgepulses all the time
  • animated-card.html
  • Browser
MyFirst Animated Card

You build a fully animated profile card — with CSS only, no JavaScript. Page-load animation, hover effects, a pulse badge, the lot.

  • Card fades in on load (stagger)
  • Avatar with a pulse-ring animation
  • Hover lifts the card (lift)
  • Button with a shine effect on hover
  • Badge pulses continuously

Create a file animated-card.html and paste in the complete code:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Animated Card</title>
  <style>
    /* ── RESET & BASE ── */
    * { margin:0; padding:0; box-sizing:border-box; }
    body {
      min-height: 100vh;
      background: #0a0a0f;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: system-ui, sans-serif;
    }

    /* ── FADE-IN ANIMATION ── */
    @keyframes fadeUp {
      from { opacity: 0; transform: translateY(30px); }
      to   { opacity: 1; transform: translateY(0); }
    }

    /* ── CARD ── */
    .card {
      background: #18181b;
      border: 1px solid #2e2e35;
      border-radius: 20px;
      padding: 36px 32px;
      width: 300px;
      text-align: center;
      transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1),
                  box-shadow 0.35s ease;

      /* Stagger: fade in the card itself */
      opacity: 0;
      animation: fadeUp 0.6s ease 0.1s forwards;
    }
    .card:hover {
      transform: translateY(-10px);
      box-shadow: 0 30px 60px rgba(0,0,0,0.5);
    }

    /* ── AVATAR with PULSE ── */
    @keyframes pulse-ring {
      0%   { box-shadow: 0 0 0 0 rgba(250,92,123,0.6); }
      70%  { box-shadow: 0 0 0 14px rgba(250,92,123,0); }
      100% { box-shadow: 0 0 0 0 rgba(250,92,123,0); }
    }
    .avatar-wrap {
      display: inline-block;
      margin-bottom: 20px;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.25s forwards;
    }
    .avatar {
      width: 80px; height: 80px;
      border-radius: 50%;
      background: linear-gradient(135deg, #fa5c7b, #a78bfa);
      display: flex; align-items: center; justify-content: center;
      font-size: 2rem;
      animation: pulse-ring 2s ease-out infinite;
    }

    /* ── NAME & BIO ── */
    .name {
      font-size: 1.3rem;
      font-weight: 700;
      color: #f4f4f5;
      margin-bottom: 6px;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.35s forwards;
    }
    .bio {
      font-size: 0.88rem;
      color: #71717a;
      margin-bottom: 20px;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.45s forwards;
    }

    /* ── BADGE ── */
    @keyframes badge-pulse {
      0%,100% { opacity: 1; }
      50%     { opacity: 0.5; }
    }
    .badge {
      display: inline-flex; align-items: center; gap: 6px;
      background: rgba(250,92,123,0.15);
      border: 1px solid rgba(250,92,123,0.4);
      color: #fa5c7b;
      padding: 4px 12px;
      border-radius: 50px;
      font-size: 0.78rem;
      margin-bottom: 24px;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.5s forwards;
    }
    .badge-dot {
      width: 6px; height: 6px;
      background: #fa5c7b;
      border-radius: 50%;
      animation: badge-pulse 1.2s ease infinite;
    }

    /* ── BUTTON with SHINE ── */
    @keyframes shine {
      from { left: -100%; }
      to   { left: 150%; }
    }
    .btn {
      display: block;
      background: #fa5c7b;
      color: #fff;
      border: none;
      padding: 12px 28px;
      border-radius: 10px;
      font-size: 0.9rem;
      font-weight: 600;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: transform 0.2s ease, background 0.2s;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.6s forwards;
    }
    .btn::after {
      content: '';
      position: absolute;
      top: -50%; left: -100%;
      width: 60%; height: 200%;
      background: rgba(255,255,255,0.25);
      transform: skewX(-20deg);
      transition: none;
    }
    .btn:hover::after {
      animation: shine 0.5s ease;
    }
    .btn:hover {
      background: #e04460;
      transform: scale(1.03);
    }

    /* ── STATS ── */
    .stats {
      display: flex;
      justify-content: space-around;
      margin-top: 24px;
      padding-top: 20px;
      border-top: 1px solid #2e2e35;
      opacity: 0;
      animation: fadeUp 0.6s ease 0.7s forwards;
    }
    .stat-value { font-size: 1.2rem; font-weight: 700; color: #f4f4f5; }
    .stat-label { font-size: 0.72rem; color: #71717a; margin-top: 2px; }
  </style>
</head>
<body>

  <div class="card">

    <div class="avatar-wrap">
      <div class="avatar">🚀</div>
    </div>

    <div class="name">Your Name</div>
    <div class="bio">CSS Animator · Creative Coder</div>

    <div class="badge">
      <span class="badge-dot"></span>
      Online
    </div>

    <button class="btn">View profile →</button>

    <div class="stats">
      <div>
        <div class="stat-value">42</div>
        <div class="stat-label">Projects</div>
      </div>
      <div>
        <div class="stat-value">128</div>
        <div class="stat-label">Followers</div>
      </div>
      <div>
        <div class="stat-value">7</div>
        <div class="stat-label">Awards</div>
      </div>
    </div>

  </div>

</body>
</html>

The aha moment

Open the file in your browser — every element fades in with a delay, the avatar pulses, the card rises on hover, the button lights up. All pure CSS, zero JavaScript. That is the power of @keyframes + transitions combined!

Bonus challenges

If you fancy it, try these extensions:

Chapter 08

Cheat sheet

Assembly planWhich tool when?

The cheat sheet below, as a signpost.

Your movementWhat should happen?
Answer
From A to B on hovertransition
Own sequence, even without hover@keyframes + animation
Position, rotation, sizetransform

Transitions

transition: all .3s ease
Animate everything
transition: color .2s, transform .4s
Several properties
transition: transform .3s ease-out
Transform only (GPU)

@keyframes basic pattern

from {} to {}
Simple A→B transition
0% {} 50% {} 100% {}
Several intermediate points

animation shorthand

animation: name .6s ease forwards
Once, keep the end state
animation: spin 1s linear infinite
Spin forever
animation: pulse 2s ease-out infinite
Pulse forever
animation-delay: .2s
Delay
animation-direction: alternate
There and back
animation-play-state: paused
Pause via CSS/JS

Transform toolbox

translateX/Y(px)
Move (GPU!)
scale(1.5)
Scale (GPU!)
rotate(45deg)
Rotate
skewX(15deg)
Skew