Transitions
Transitions
Split Doors
The outgoing scene splits down the middle and its two halves slide apart like doors, revealing the next scene.
Install
Usage
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={60}>
<TitleCard />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={splitDoors()} timing={linearTiming({ durationInFrames: 24 })} />
<TransitionSeries.Sequence durationInFrames={90}>
<ProductShot />
</TransitionSeries.Sequence>
</TransitionSeries>Props
| Name | Type | Default | Description |
|---|---|---|---|
direction? | "vertical" | "horizontal" | — | Which way the doors travel. Defaults to `horizontal` in landscape and square, `vertical` in portrait. |
shadowColor? | string | — | Shadow the doors cast on the revealed scene. |
Use
- Revealing a product shot, logo or new chapter behind the current scene
- Moving from a title card into the main content
Avoid
- An outgoing scene that plays audio or video, since it renders once per door — use
circle-burst
Dependencies
@remotion/transitions, remotion
Source
registry/items/split-doors.tsx
/**
* @title Split Doors
* @category transitions
* @description The outgoing scene splits down the middle and its two halves slide apart like doors, revealing the next scene.
* @duration 24
* @use Revealing a product shot, logo or new chapter behind the current scene
* @use Moving from a title card into the main content
* @avoid An outgoing scene that plays audio or video, since it renders once per door — use `circle-burst`
* @tags doors, split, open, reveal
* @example
* <TransitionSeries>
* <TransitionSeries.Sequence durationInFrames={60}>
* <TitleCard />
* </TransitionSeries.Sequence>
* <TransitionSeries.Transition presentation={splitDoors()} timing={linearTiming({ durationInFrames: 24 })} />
* <TransitionSeries.Sequence durationInFrames={90}>
* <ProductShot />
* </TransitionSeries.Sequence>
* </TransitionSeries>
*/
import type { TransitionPresentation, TransitionPresentationComponentProps } from "@remotion/transitions";
import { AbsoluteFill, interpolate } from "remotion";
import { alpha, CLAMP, easings, useViewport } from "./core";
export type SplitDoorsProps = {
/** Which way the doors travel. Defaults to `horizontal` in landscape and square, `vertical` in portrait. */
direction?: "horizontal" | "vertical";
/** Shadow the doors cast on the revealed scene. */
shadowColor?: string;
};
function SplitDoorsPresentation({
children,
presentationDirection,
presentationProgress,
passedProps,
}: TransitionPresentationComponentProps<SplitDoorsProps>) {
const { width, height, isPortrait, u } = useViewport();
const horizontal = (passedProps.direction ?? (isPortrait ? "vertical" : "horizontal")) === "horizontal";
const open = interpolate(presentationProgress, [0, 1], [0, 1], { ...CLAMP, easing: easings.gentle });
if (presentationDirection === "entering") {
// The incoming scene shows through the widening gap, shaded at the door edges and settling from a slight zoom.
const gap = (1 - open) * 50;
const shadow = passedProps.shadowColor ?? alpha("#000000", 0.5);
const fade = u(120);
return (
<AbsoluteFill style={{ clipPath: horizontal ? `inset(0 ${gap}%)` : `inset(${gap}% 0)` }}>
<AbsoluteFill style={{ scale: String(1.08 - 0.08 * open) }}>{children}</AbsoluteFill>
<AbsoluteFill
style={{
opacity: 1 - open,
background: `linear-gradient(${horizontal ? "90deg" : "180deg"}, ${shadow} ${gap}%, transparent calc(${gap}% + ${fade}px), transparent calc(${100 - gap}% - ${fade}px), ${shadow} ${100 - gap}%)`,
}}
/>
</AbsoluteFill>
);
}
// The outgoing scene is drawn once per door; each door shows its half and travels its own size off-screen.
return (
<AbsoluteFill>
{[0, 1].map((door) => {
const sign = door === 0 ? -1 : 1;
return (
<div
key={door}
style={{
position: "absolute",
overflow: "hidden",
left: horizontal ? (door * width) / 2 : 0,
top: horizontal ? 0 : (door * height) / 2,
width: horizontal ? width / 2 : width,
height: horizontal ? height : height / 2,
translate: horizontal ? `${sign * open * 100}% 0` : `0 ${sign * open * 100}%`,
}}
>
<div
style={{
position: "absolute",
left: horizontal ? (-door * width) / 2 : 0,
top: horizontal ? 0 : (-door * height) / 2,
width,
height,
}}
>
{children}
</div>
</div>
);
})}
</AbsoluteFill>
);
}
export function splitDoors(props: SplitDoorsProps = {}): TransitionPresentation<SplitDoorsProps> {
return { component: SplitDoorsPresentation, props };
}