# Drift
URL: https://remocn.dev/docs/layout/drift

Wrap any scene in a slow camera push-in so no frame is ever static

- Install: `npx shadcn@latest add @remocn/drift`
- Vibe: clean
- Natural length: 90f @ 30fps

## Installation

```bash
npx shadcn@latest add @remocn/drift
```

## Usage

`Drift` wraps scene content in a constant-speed camera push-in, scaling it from `1` to `1 + grow` over the wrapping sequence's duration. The movement is linear on purpose — a constant creep that adds life to a scene without drawing attention to itself.

`durationInFrames` comes from `useVideoConfig()`, which is Sequence-scoped. Inside a `TransitionSeries.Sequence`, the drift stretches exactly across that scene's own duration and peaks right at the cut. Put `Drift` inside the scene, under the transition, not around the whole series.

A `grow` of `0.03` to `0.05` is the working range — beyond that it starts to read as a deliberate zoom rather than ambient motion. A negative `grow` turns it into a slow pull-back instead of a push-in.

```tsx
import { Drift } from "@/components/remocn/drift";

export const MyScene = () => (
  <Drift>
    <YourScene />
  </Drift>
);
```

Inside a `TransitionSeries.Sequence`:

```tsx
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { Drift } from "@/components/remocn/drift";
import { zoomBlur } from "@/components/remocn/zoom-blur";

export const MyVideo = () => (
  <TransitionSeries>
    <TransitionSeries.Sequence durationInFrames={90}>
      <Drift>
        <SceneA />
      </Drift>
    </TransitionSeries.Sequence>
    <TransitionSeries.Transition
      timing={linearTiming({ durationInFrames: 18 })}
      presentation={zoomBlur()}
    />
    <TransitionSeries.Sequence durationInFrames={90}>
      <Drift>
        <SceneB />
      </Drift>
    </TransitionSeries.Sequence>
  </TransitionSeries>
);
```

## Text under drift

A slow push-in keeps every glyph in sub-pixel motion, and a glyph raster can only sit on a whole device pixel — so type inside a plain `Drift` trembles while borders right next to it glide. Give text blocks their own compositor layer, and the zoom moves a cached texture instead of re-rasterizing the glyphs every frame:

```tsx
<Drift>
  <div style={{ border: "1px solid rgba(255,255,255,0.08)" }}>
    <div style={{ willChange: "transform" }}>
      <h1>Quarterly Report</h1>
      <p>Revenue grew across every region this quarter.</p>
    </div>
  </div>
</Drift>
```

One layer per text container — never per word or character, and never on `Drift` itself. Keep borders and other hairline geometry outside the promoted layer: as part of a resampled texture, a 1px line pulses between sharp and blurry as the scale creeps, while fresh rasterization every frame lets it glide.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `grow` | `number` | `0.035` | Scale gain reached at the end of the scene. 0.035 means the scene ends 3.5 percent larger. Negative values pull back instead. |

## Use when
- Any static scene should feel alive — hero cards, feature panels, dashboards sitting between transitions.
- You want the default anti-static wrapper of the catalog — reach for it whenever a scene would otherwise hold a frozen frame.

## Don't use when
- The content is already animated with strong motion — layering drift on top of it doubles the movement and muddies the read.
- You need a noticeable, dramatic zoom — this is a creep, not a statement; use a real scale animation or `zoom-blur` for punch.