# CSS @keyframes Deep Dive

A comprehensive guide for Angular developers transitioning from `@angular/animations` to native CSS animations.

---

## Table of Contents

1. [Understanding @keyframes](#understanding-keyframes)
2. [Basic Syntax](#basic-syntax)
3. [Animation Properties](#animation-properties)
4. [Timing Functions (Easing)](#timing-functions-easing)
5. [Fill Modes](#fill-modes)
6. [Advanced Techniques](#advanced-techniques)
7. [Angular 20+ Integration](#angular-20-integration)
8. [Common Patterns & Recipes](#common-patterns--recipes)
9. [Performance Tips](#performance-tips)
10. [Debugging Animations](#debugging-animations)

---

## Understanding @keyframes

The `@keyframes` at-rule controls the intermediate steps in a CSS animation sequence by defining styles for keyframes (waypoints) along the animation. Unlike transitions (which only animate between two states), keyframes let you define multiple intermediate steps.

### How It Differs from @angular/animations

| @angular/animations | Native CSS @keyframes |
|---------------------|----------------------|
| ~60KB JavaScript bundle | Zero JS overhead |
| CPU-based rendering | GPU hardware acceleration |
| Angular-specific syntax | Standard CSS (transferable skills) |
| `trigger()`, `state()`, `animate()` | `@keyframes` + CSS classes |

---

## Basic Syntax

### The @keyframes Rule

```css
@keyframes animation-name {
  from {
    /* Starting styles (same as 0%) */
  }
  to {
    /* Ending styles (same as 100%) */
  }
}
```

### Percentage-Based Keyframes

For more control, use percentages to define multiple waypoints:

```css
@keyframes bounce {
  0% {
    transform: translateY(0);
  }
  25% {
    transform: translateY(-30px);
  }
  50% {
    transform: translateY(0);
  }
  75% {
    transform: translateY(-15px);
  }
  100% {
    transform: translateY(0);
  }
}
```

### Combining Multiple Percentages

You can apply the same styles to multiple keyframes:

```css
@keyframes pulse {
  0%, 100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.7;
    transform: scale(1.05);
  }
}
```

### Applying the Animation

```css
.element {
  animation: bounce 1s ease-in-out infinite;
}
```

---

## Animation Properties

### Individual Properties

| Property | Description | Example |
|----------|-------------|---------|
| `animation-name` | Name of the @keyframes | `animation-name: bounce;` |
| `animation-duration` | How long one cycle takes | `animation-duration: 2s;` |
| `animation-timing-function` | Speed curve (easing) | `animation-timing-function: ease-in;` |
| `animation-delay` | Wait before starting | `animation-delay: 500ms;` |
| `animation-iteration-count` | How many times to run | `animation-iteration-count: 3;` or `infinite` |
| `animation-direction` | Forward, reverse, or alternate | `animation-direction: alternate;` |
| `animation-fill-mode` | Styles before/after animation | `animation-fill-mode: forwards;` |
| `animation-play-state` | Pause or play | `animation-play-state: paused;` |

### Shorthand Syntax

```css
/* animation: name duration timing-function delay iteration-count direction fill-mode play-state */
.element {
  animation: slideIn 0.5s ease-out 0.2s 1 normal forwards running;
}
```

**Minimum required:** name and duration

```css
.element {
  animation: fadeIn 1s;
}
```

### Multiple Animations

Apply multiple animations to a single element:

```css
.element {
  animation:
    fadeIn 0.5s ease-out,
    slideUp 0.5s ease-out,
    pulse 2s ease-in-out 0.5s infinite;
}
```

---

## Timing Functions (Easing)

The timing function controls how the animation progresses over time—where it speeds up and slows down.

### Keyword Values

| Keyword | Cubic-Bezier Equivalent | Description |
|---------|------------------------|-------------|
| `linear` | `cubic-bezier(0, 0, 1, 1)` | Constant speed |
| `ease` | `cubic-bezier(0.25, 0.1, 0.25, 1)` | Default: slow start, fast middle, slow end |
| `ease-in` | `cubic-bezier(0.42, 0, 1, 1)` | Slow start, fast end |
| `ease-out` | `cubic-bezier(0, 0, 0.58, 1)` | Fast start, slow end |
| `ease-in-out` | `cubic-bezier(0.42, 0, 0.58, 1)` | Slow start and end |

### Custom Cubic-Bezier

Create custom easing curves with `cubic-bezier(x1, y1, x2, y2)`:

```css
/* Bouncy overshoot effect */
.element {
  animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6);
}

/* Smooth deceleration */
.element {
  animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
}
```

**Tool:** Use [cubic-bezier.com](https://cubic-bezier.com) to visualize and create custom curves.

### Popular Easing Functions

```css
/* Ease Out Quart - Great for enter animations */
cubic-bezier(0.25, 1, 0.5, 1)

/* Ease In Out Cubic - Smooth state changes */
cubic-bezier(0.65, 0, 0.35, 1)

/* Ease Out Back - Slight overshoot */
cubic-bezier(0.34, 1.56, 0.64, 1)

/* Ease In Out Back - Overshoot both ends */
cubic-bezier(0.68, -0.6, 0.32, 1.6)
```

### Steps Function

For frame-by-frame animations (like sprite sheets):

```css
/* 6 discrete steps */
.sprite {
  animation: walk 1s steps(6) infinite;
}

/* Step positions */
steps(4, jump-start)  /* Jump at start of each interval */
steps(4, jump-end)    /* Jump at end of each interval (default) */
steps(4, jump-both)   /* Jump at both ends */
steps(4, jump-none)   /* No jump at ends */
```

### Timing Function Per Keyframe

Apply different easing to different segments:

```css
@keyframes complexMove {
  0% {
    transform: translateX(0);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateX(100px);
    animation-timing-function: ease-in;
  }
  100% {
    transform: translateX(200px);
  }
}
```

**Important:** The timing function applies to each step individually, not the entire animation.

---

## Fill Modes

Fill modes control what styles apply before and after the animation runs.

### Values

| Value | Before Animation | After Animation |
|-------|-----------------|-----------------|
| `none` | Original styles | Original styles |
| `forwards` | Original styles | **Last keyframe styles** |
| `backwards` | **First keyframe styles** | Original styles |
| `both` | **First keyframe styles** | **Last keyframe styles** |

### Common Problem: Element Snaps Back

```css
/* BAD: Element disappears then reappears after animation */
@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}

.element {
  animation: fadeOut 1s; /* Element snaps back to opacity: 1 */
}

/* GOOD: Element stays invisible */
.element {
  animation: fadeOut 1s forwards;
}
```

### Backwards Fill Mode (for delays)

```css
@keyframes slideIn {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Without backwards: element visible at original position during delay */
/* With backwards: element starts at first keyframe position during delay */
.element {
  animation: slideIn 0.5s ease-out 1s backwards;
}
```

---

## Advanced Techniques

### Animation Direction

Control playback direction:

```css
animation-direction: normal;           /* 0% → 100% */
animation-direction: reverse;          /* 100% → 0% */
animation-direction: alternate;        /* 0% → 100% → 0% */
animation-direction: alternate-reverse; /* 100% → 0% → 100% */
```

**Use Case:** Breathing/pulsing effects

```css
@keyframes breathe {
  from { transform: scale(1); }
  to { transform: scale(1.1); }
}

.element {
  animation: breathe 2s ease-in-out infinite alternate;
}
```

### Staggered Animations

Create cascading effects with animation-delay:

```css
.item { animation: fadeSlideIn 0.5s ease-out backwards; }
.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 100ms; }
.item:nth-child(3) { animation-delay: 200ms; }
.item:nth-child(4) { animation-delay: 300ms; }

/* Or use CSS custom properties */
.item {
  animation: fadeSlideIn 0.5s ease-out backwards;
  animation-delay: calc(var(--i, 0) * 100ms);
}
```

In your template:

```html
<div class="item" style="--i: 0">First</div>
<div class="item" style="--i: 1">Second</div>
<div class="item" style="--i: 2">Third</div>
```

### @starting-style (Modern CSS)

Define styles for when an element first enters the DOM:

```css
.modal {
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.3s, transform 0.3s;

  @starting-style {
    opacity: 0;
    transform: scale(0.9);
  }
}
```

### Animating Auto Height

Use CSS Grid for height: auto animations:

```css
.accordion-content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease-out;
}

.accordion-content.open {
  grid-template-rows: 1fr;
}

.accordion-content > div {
  overflow: hidden;
}
```

### Pause/Play with CSS

```css
.element {
  animation: spin 2s linear infinite;
  animation-play-state: running;
}

.element:hover {
  animation-play-state: paused;
}

/* Or with a class */
.element.paused {
  animation-play-state: paused;
}
```

---

## Angular 20+ Integration

### Using animate.enter and animate.leave

Angular 20.2+ provides `animate.enter` and `animate.leave` to apply CSS classes when elements enter/leave the DOM.

```typescript
@Component({
  selector: 'app-example',
  template: `
    @if (isVisible()) {
      <div animate.enter="fade-in" animate.leave="fade-out">
        Content here
      </div>
    }
    <button (click)="toggle()">Toggle</button>
  `,
  styles: [`
    .fade-in {
      animation: fadeIn 0.3s ease-out;
    }

    .fade-out {
      animation: fadeOut 0.3s ease-in;
    }

    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }

    @keyframes fadeOut {
      from { opacity: 1; }
      to { opacity: 0; }
    }
  `]
})
export class ExampleComponent {
  isVisible = signal(false);
  toggle() { this.isVisible.update(v => !v); }
}
```

### Dynamic Animation Classes

```typescript
@Component({
  template: `
    @if (show()) {
      <div [animate.enter]="enterAnimation()" [animate.leave]="leaveAnimation()">
        Dynamic animations!
      </div>
    }
  `
})
export class DynamicAnimComponent {
  show = signal(false);
  enterAnimation = signal('slide-in-right');
  leaveAnimation = signal('slide-out-left');
}
```

### Reusable Animation CSS File

Create a shared `animations.css`:

```css
/* animations.css */

/* Fade animations */
.fade-in { animation: fadeIn 0.3s ease-out; }
.fade-out { animation: fadeOut 0.3s ease-in; }

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Slide animations */
.slide-in-up { animation: slideInUp 0.3s ease-out; }
.slide-out-down { animation: slideOutDown 0.3s ease-in; }

@keyframes slideInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideOutDown {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(20px);
  }
}

/* Scale animations */
.scale-in { animation: scaleIn 0.2s ease-out; }
.scale-out { animation: scaleOut 0.2s ease-in; }

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes scaleOut {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.9);
  }
}
```

Import in `styles.css` or `angular.json`:

```css
@import 'animations.css';
```

---

## Common Patterns & Recipes

### Loading Spinner

```css
@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #f3f3f3;
  border-top: 3px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
```

### Skeleton Loading

```css
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}
```

### Attention Pulse

```css
@keyframes attention-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5);
  }
  50% {
    box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
  }
}

.notification-badge {
  animation: attention-pulse 2s ease-in-out infinite;
}
```

### Shake (Error Feedback)

```css
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
  20%, 40%, 60%, 80% { transform: translateX(5px); }
}

.error-input {
  animation: shake 0.5s ease-in-out;
}
```

### Slide Down Menu

```css
@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.dropdown-menu {
  animation: slideDown 0.2s ease-out forwards;
}
```

### Toast Notification

```css
@keyframes toastIn {
  from {
    opacity: 0;
    transform: translateY(100%) scale(0.9);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes toastOut {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(100%) scale(0.9);
  }
}

.toast {
  animation: toastIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.toast.leaving {
  animation: toastOut 0.2s ease-in forwards;
}
```

---

## Performance Tips

### Use Transform and Opacity

These properties are GPU-accelerated and don't trigger layout:

```css
/* GOOD - GPU accelerated */
@keyframes good {
  from { transform: translateX(0); opacity: 0; }
  to { transform: translateX(100px); opacity: 1; }
}

/* AVOID - Triggers layout recalculation */
@keyframes avoid {
  from { left: 0; width: 100px; }
  to { left: 100px; width: 200px; }
}
```

### Use will-change Sparingly

```css
.element {
  will-change: transform, opacity;
}

/* Remove after animation */
.element.animation-complete {
  will-change: auto;
}
```

### Respect Reduced Motion

```css
@keyframes fadeSlide {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.element {
  animation: fadeSlide 0.3s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none;
    /* Or use a simpler fade */
    animation: fadeIn 0.1s ease-out;
  }
}
```

### Avoid Animating Layout Properties

Properties that trigger layout (reflow):

- `width`, `height`
- `top`, `left`, `right`, `bottom`
- `margin`, `padding`
- `font-size`
- `border-width`

Use `transform: scale()` instead of `width/height` when possible.

---

## Debugging Animations

### Browser DevTools

1. **Chrome DevTools** → More Tools → Animations
   - Pause, slow down, or step through animations
   - Inspect timing curves

2. **Firefox** → Inspector → Animations tab
   - Visual timeline of all animations

### Force Slow Motion

```css
/* Temporarily add to debug */
* {
  animation-duration: 3s !important;
}
```

### Animation Events in JavaScript

```typescript
element.addEventListener('animationstart', (e) => {
  console.log('Started:', e.animationName);
});

element.addEventListener('animationend', (e) => {
  console.log('Ended:', e.animationName);
  // Clean up class, remove element, etc.
});

element.addEventListener('animationiteration', (e) => {
  console.log('Iteration:', e.animationName);
});
```

### Common Issues

| Problem | Solution |
|---------|----------|
| Animation not running | Check `animation-duration` is > 0 |
| Element snaps back | Add `animation-fill-mode: forwards` |
| Animation starts wrong | Use `animation-fill-mode: backwards` with delay |
| Choppy animation | Use `transform` instead of layout properties |
| Animation restarts on state change | Ensure Angular doesn't recreate the element |

---

## Quick Reference Card

```css
/* Basic setup */
@keyframes name {
  from { /* start */ }
  to { /* end */ }
}

.element {
  animation: name 0.3s ease-out forwards;
}

/* Angular 20+ */
<div animate.enter="fade-in" animate.leave="fade-out">

/* Shorthand order */
animation: name duration timing delay count direction fill-mode state;

/* Common timing functions */
ease-out: cubic-bezier(0, 0, 0.58, 1)      /* Enter animations */
ease-in: cubic-bezier(0.42, 0, 1, 1)       /* Exit animations */
ease-in-out: cubic-bezier(0.42, 0, 0.58, 1) /* State changes */

/* Fill modes */
forwards  → Keep end state
backwards → Apply start state during delay
both      → Both of the above
```

---

## Resources

- [MDN CSS Animations Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)
- [Angular Animation Migration Guide](https://angular.dev/guide/animations/migration)
- [Cubic Bezier Tool](https://cubic-bezier.com)
- [Easing Functions Cheat Sheet](https://easings.net)
- [Josh W. Comeau's Keyframe Guide](https://www.joshwcomeau.com/animation/keyframe-animations/)
