Stage
Put a screenshot, recording, or long page on a perspective studio plane and direct the camera across it with timeline keyframes
Installation
$ pnpm dlx shadcn@latest add @remocn/stageUsage
Stage is both the set and the operator camera. It places one child surface in a perspective studio, then keeps a normalized point on that surface at the center of the shot as moves change over time.
import { Img, staticFile } from "remotion";
import { Stage } from "@/components/remocn/stage";
export const WebsiteTour = () => (
<Stage
contentSize={{ width: 1440, height: 9800 }}
moves={[
{ at: 0, x: 0.5, y: 0.04, zoom: 1.02 },
{ at: 45, x: 0.5, y: 0.04, zoom: 1.02 }, // hold
{ at: 130, x: 0.48, y: 0.5, zoom: 1.12 },
{ at: 220, x: 0.52, y: 0.96, zoom: 1.04 },
]}
shake={0.12}
>
<Img
src={staticFile("long-page.png")}
style={{ width: "100%", height: "100%" }}
/>
</Stage>
);The child can be an image, video, live React component, or whole Remotion scene. Descendants keep animating, but they share one 3D plane. The plane settles into the studio over the first 24 frames; the camera path can continue for as long as the Sequence does.
Long surfaces and camera targets
contentSize declares the surface's intrinsic aspect ratio. Stage fits its width to the composition and allows the resulting height to extend far beyond the frame. It does not fake webpage scrolling: the long surface stays intact while the camera flies above it.
x and y are normalized target points on that surface:
x: 0,y: 0targets the top-left corner.x: 0.5,y: 0.5targets the center.x: 1,y: 1targets the bottom-right corner.
Stage clamps x and y to 0–1. Because the current target is held at composition center, the coordinates stay stable when zoom and perspective change. An empty moves array leaves the surface centered and static.
Stage cannot infer the intended height of an absolutely positioned child. Pass contentSize for screenshots and long layouts, and make the child fill the resulting plane.
Segment easing and holds
Each destination key can choose the easing used to arrive at it. The default is the project EXPO easing. Repeat an identical pose at a later frame to create a hold.
import { Easing } from "remotion";
<Stage
contentSize={{ width: 1440, height: 9800 }}
moves={[
{ at: 0, x: 0.5, y: 0.05, zoom: 1 },
{
at: 60,
x: 0.54,
y: 0.34,
zoom: 1.16,
easing: Easing.out(Easing.cubic),
},
{ at: 100, x: 0.54, y: 0.34, zoom: 1.16 },
{ at: 170, x: 0.48, y: 0.72, zoom: 1.08 },
]}
>
<YourLongSurface />
</Stage>Before the first key, Stage holds the first pose; after the last key, it holds the final pose. Keys are sorted by at, and when keys share a frame, the last supplied key wins. Missing pose fields use neutral values (x: 0.5, y: 0.5, zoom: 1, rotate: 0) instead of inheriting from the previous key.
Direct the studio
The studio props remain independent from the camera path:
<Stage
contentSize={{ width: 1440, height: 9800 }}
moves={moves}
backdrop="radial-gradient(circle at 35% 10%, #403a72, #111117 58%, #08080b)"
rotateX={14}
rotateY={-20}
perspective={900}
scale={0.86}
radius={1.8}
reflection={0.2}
shadow={0.8}
light={0.65}
>
<YourProductScene />
</Stage>Lower perspective values exaggerate depth. reflection, shadow, and light clamp to 0–1. radius is a percentage of the composition width, so the corner treatment scales across render sizes.
The customizer deliberately exposes experimental ranges: both rotations reach -180–180°, perspective reaches down to 50px, and scale reaches 0.1–3. Rotations beyond 90 degrees reveal the browser's naturally mirrored back face instead of making the surface disappear. Very low perspective and high scale can clip most of the surface; that is intentional for extreme close-ups and abstract depth treatments.
Deterministic handheld movement
shake adds low-frequency operator movement and smoothly interpolated seeded noise. The same seed, frame, and strength always produce the same render.
<Stage moves={moves} shake={0.18} seed="homepage-take-03">
<YourScene />
</Stage>Use 0.08–0.2 for a restrained human operator. shake clamps to 0–1.
Text and source bleed
Fractional zoom continuously resamples glyphs. If small live text trembles, put willChange: "transform" on each text container, not every word. Keep 1px borders outside those promoted layers so borders stay sharp.
The outer frame clips the surface. Targets near an edge, zoom below 1, and strong perspective can reveal the backdrop around it. Leave breathing room in the source or keep edge targets slightly inset, such as y: 0.04 and y: 0.96.
For an optical finish, wrap Stage in CameraLens. Stage supplies the physical set and operator path; CameraLens supplies bloom, softness, chromatic aberration, and vignette.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The image, video, component, or scene rendered as one studio plane. |
contentSize | { width: number; height: number } | composition size | Intrinsic surface dimensions used to preserve its aspect ratio, including long pages. |
moves | StageKey[] | [] | Normalized camera targets keyed by Sequence-local frame. |
shake | number | 0 | Deterministic handheld strength, clamped from 0 to 1. |
seed | string | "remocn-stage" | Seed used by the handheld noise field. |
backdrop | string | "linear-gradient(145deg, #17181d 0%, #09090b 72%)" | CSS background used for the full-frame studio. |
rotateX | number | 14 | X-axis tilt of the surface in degrees. |
rotateY | number | -20 | Y-axis tilt of the surface in degrees. |
perspective | number | 900 | CSS perspective distance in pixels. Lower values exaggerate depth. |
scale | number | 0.86 | Base scale of the studio plane before keyframe zoom. |
radius | number | 1.4 | Surface corner radius as a percentage of composition width. |
reflection | number | 0.24 | Floor reflection strength from 0 to 1. |
shadow | number | 0.7 | Contact-shadow strength from 0 to 1. |
light | number | 0.55 | Directional studio-light strength from 0 to 1. |
className | string | undefined | Additional class names applied to the full-frame wrapper. |
StageKey
| Prop | Type | Default | Description |
|---|---|---|---|
at | number | — | Required frame from the start of the current Sequence. |
x | number | 0.5 | Horizontal target on the surface from left (0) to right (1). |
y | number | 0.5 | Vertical target on the surface from top (0) to bottom (1). |
zoom | number | 1 | Zoom multiplier around the current target. |
rotate | number | 0 | Camera roll in degrees; the surface rotates in the opposite direction. |
easing | EasingFunction | EXPO | Easing used by the segment arriving at this key. |