# remotion-animated

A declarative animation library for Remotion that simplifies motion by putting animation logic directly in JSX.

**Website:** https://www.remotion-animated.dev
**GitHub:** https://github.com/stefanwittwer/remotion-animated

## Installation

```bash
npm i remotion-animated
```

## Why Use It

- **Declarative:** Animation logic lives in JSX, right next to the element being animated
- **Spring defaults:** Animations look great out-of-the-box without tweaking physics
- **Composable:** Chain multiple timed animations easily
- **Less boilerplate:** Reduces `interpolate()` calls at the top of components

## The `<Animated>` Component

Wrap any element to animate it:

```tsx
import { Animated, Move, Scale, Fade, Rotate } from 'remotion-animated';

const MyComposition = () => (
  <AbsoluteFill>
    <Animated
      animations={[
        Scale({ by: 1, initial: 10 }),      // Start at 10x, scale to 1x
        Move({ y: -40, start: 50 }),        // Move up at frame 50
        Move({ y: 40, start: 100 }),        // Move down at frame 100
        Fade({ to: 0, start: 150 }),        // Fade out at frame 150
        Scale({ by: 0, start: 150, mass: 75 }), // Shrink at frame 150
      ]}
    >
      <HelloWorld />
    </Animated>
  </AbsoluteFill>
);
```

### Props

| Prop | Type | Description |
|------|------|-------------|
| `animations` | `Animation[]` | List of animations (order matters for same property) |
| `absolute?` | `boolean` | Position container absolutely. Default: `false` |
| `in?` | `number` | Frame when container becomes visible |
| `out?` | `number` | Frame when container stops being visible |
| `delay?` | `number` | Delay all animation starts by N frames. Default: `0` |
| `className?` | `string` | Class name for container |
| `style?` | `React.CSSProperties` | Styles for container |

---

## Animations

### Move

Translates an element horizontally, vertically, or both.

```tsx
import { Animated, Move } from 'remotion-animated';

// Move right 200px
<Animated animations={[Move({ x: 200 })]}>
  <Text />
</Animated>

// Move down 80px and left 20px
<Animated animations={[Move({ x: -20, y: 80 })]}>
  <Text />
</Animated>

// Chain multiple moves
<Animated animations={[
  Move({ x: 20, y: 80 }),
  Move({ x: 20, y: -80, start: 20 }),
  Move({ x: 40, start: 40 }),
]}>
  <Text />
</Animated>
```

**Options:**
- `x?`, `y?`, `z?` — Target position offset (pixels)
- `initialX?`, `initialY?`, `initialZ?` — Starting position (default: 0)

---

### Scale

Scales an element proportionally or per-axis.

```tsx
import { Animated, Scale } from 'remotion-animated';

// Scale up to 1.5x
<Animated animations={[Scale({ by: 1.5 })]}>
  <Text />
</Animated>

// Pop in from 0
<Animated animations={[Scale({ by: 1, initial: 0 })]}>
  <Text />
</Animated>

// Zoom in from far away
<Animated animations={[Scale({ by: 1, initial: 10 })]}>
  <Text />
</Animated>
```

**Options:**
- `by?` — Proportional scale factor (1 = same size, 2 = 2x, 0.5 = half)
- `x?`, `y?`, `z?` — Per-axis scale factors
- `initial?` — Starting scale factor (default: 1)
- `initialX?`, `initialY?`, `initialZ?` — Per-axis starting scales

---

### Rotate

Rotates an element clockwise (2D or 3D).

```tsx
import { Animated, Rotate } from 'remotion-animated';

// Rotate 180 degrees
<Animated animations={[Rotate({ degrees: 180 })]}>
  <Text />
</Animated>

// Full rotation
<Animated animations={[Rotate({ degrees: 360 })]}>
  <Icon />
</Animated>

// 3D rotation (requires perspective on parent)
<div style={{ perspective: '1200px' }}>
  <Animated animations={[Rotate({ degreesX: 180 })]}>
    <Card />
  </Animated>
</div>
```

**Options:**
- `degrees?` — Z-axis rotation (2D)
- `degreesX?`, `degreesY?` — 3D rotation (requires perspective)
- `initial?` — Starting angle (default: 0)

---

### Fade

Animates opacity.

```tsx
import { Animated, Fade } from 'remotion-animated';

// Fade in
<Animated animations={[Fade({ to: 1, initial: 0 })]}>
  <Text />
</Animated>

// Fade out at frame 60
<Animated animations={[Fade({ to: 0, start: 60 })]}>
  <Text />
</Animated>

// Fade in then out
<Animated animations={[
  Fade({ to: 1, initial: 0, start: 20 }),
  Fade({ to: 0, start: 60 }),
]}>
  <Text />
</Animated>
```

**Options:**
- `to?` — Target opacity (0 = invisible, 1 = opaque)
- `initial?` — Starting opacity (default: 1)

---

### Size

Animates width/height (unlike Scale, doesn't affect children).

```tsx
import { Animated, Size } from 'remotion-animated';

// Animate width to 300px
<Animated animations={[Size({ width: 300 })]}>
  <Box />
</Animated>

// Reveal from 0 height
<Animated animations={[Size({ height: 200, initialHeight: 0 })]}>
  <Card />
</Animated>
```

**Options:**
- `width?`, `height?` — Target dimensions
- `initialWidth?`, `initialHeight?` — Starting dimensions (default: 0)

---

## Common Animation Options

All animations accept these timing/physics options:

### Timing

| Option | Type | Description |
|--------|------|-------------|
| `start?` | `number` | Frame to start animation. Default: `0` |
| `duration?` | `number` | Frames for animation. Default: 15 for custom easing |
| `ease?` | `EasingBehaviour` | Custom easing (overrides spring) |

### Spring Physics

Uses Remotion's spring by default. Override with:

| Option | Type | Description |
|--------|------|-------------|
| `mass?` | `number` | Higher = slower, more momentum |
| `damping?` | `number` | Higher = less bounce |
| `stiffness?` | `number` | Higher = faster, snappier |
| `overshootClamping?` | `boolean` | Stop at target (no overshoot) |

---

## Combining Animations

Animations stack and can be timed independently:

```tsx
// Classic entrance: fade + scale + move
<Animated animations={[
  Fade({ to: 1, initial: 0 }),
  Scale({ by: 1, initial: 0.8 }),
  Move({ y: 0, initialY: 20 }),
]}>
  <Card />
</Animated>

// Staggered with delays
<Animated animations={[
  Fade({ to: 1, initial: 0 }),
  Move({ y: 0, initialY: 50, start: 5 }),
  Scale({ by: 1, initial: 0.9, start: 10 }),
]}>
  <Text />
</Animated>

// Complex multi-stage animation
<Animated animations={[
  // Stage 1: Enter
  Fade({ to: 1, initial: 0 }),
  Scale({ by: 1, initial: 0 }),
  // Stage 2: Bounce
  Move({ y: -20, start: 30 }),
  Move({ y: 0, start: 40 }),
  // Stage 3: Exit
  Fade({ to: 0, start: 90 }),
  Scale({ by: 2, start: 90 }),
]}>
  <Element />
</Animated>
```

---

## Best Practices

### Use `in` and `out` for visibility

```tsx
// Element only visible frames 30-90
<Animated in={30} out={90} animations={[Fade({ to: 1, initial: 0 })]}>
  <Text />
</Animated>
```

### Use `delay` for staggering multiple items

```tsx
{items.map((item, i) => (
  <Animated
    key={i}
    delay={i * 10}
    animations={[Fade({ to: 1, initial: 0 }), Move({ y: 0, initialY: 20 })]}
  >
    <Item data={item} />
  </Animated>
))}
```

### Set `transform-origin` for Scale/Rotate

```tsx
<Animated
  style={{ transformOrigin: 'top left' }}
  animations={[Scale({ by: 1.5 })]}
>
  <Box />
</Animated>
```

---

## When to Use remotion-animated vs Manual interpolate

**Use remotion-animated when:**
- Simple transforms: move, scale, rotate, fade
- Spring physics are fine (no custom curves needed)
- You want cleaner JSX

**Use manual `interpolate()` when:**
- Complex custom easing curves
- Animating non-transform properties (colors, gradients, blur)
- Precise multi-property coordination with shared timing
- Performance-critical scenarios with many elements
