Transitions
Transitions
Card Push
The outgoing scene shrinks into a rounded card and flies off while the next scene rises from behind as a card and expands to full frame.
Install
Usage
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={90}>
<FeatureOne />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={cardPush({ direction: "left" })} timing={linearTiming({ durationInFrames: 30 })} />
<TransitionSeries.Sequence durationInFrames={90}>
<FeatureTwo />
</TransitionSeries.Sequence>
</TransitionSeries>Props
| Name | Type | Default | Description |
|---|---|---|---|
direction? | "up" | "down" | "left" | "right" | — | Where the outgoing card leaves to. The incoming card rises from the opposite side. |
tilt? | number | — | Degrees the outgoing card tilts as it leaves. |
backdrop? | string | — | Canvas behind the cards. Defaults to a tone between the theme background and foreground. |
borderColor? | string | — | Card outline. Defaults to the theme border. |
Use
- Stepping through screens, slides or features like a deck of cards
- Product walkthroughs and app demos
Dependencies
@remotion/transitions, remotion
Source
registry/items/card-push.tsx
/**
* @title Card Push
* @category transitions
* @description The outgoing scene shrinks into a rounded card and flies off while the next scene rises from behind as a card and expands to full frame.
* @duration 30
* @use Stepping through screens, slides or features like a deck of cards
* @use Product walkthroughs and app demos
* @tags card, deck, push, stack
* @example
* <TransitionSeries>
* <TransitionSeries.Sequence durationInFrames={90}>
* <FeatureOne />
* </TransitionSeries.Sequence>
* <TransitionSeries.Transition presentation={cardPush({ direction: "left" })} timing={linearTiming({ durationInFrames: 30 })} />
* <TransitionSeries.Sequence durationInFrames={90}>
* <FeatureTwo />
* </TransitionSeries.Sequence>
* </TransitionSeries>
*/
import type { TransitionPresentation, TransitionPresentationComponentProps } from "@remotion/transitions";
import type React from "react";
import { AbsoluteFill, interpolate } from "remotion";
import { alpha, CLAMP, easings, exitEasing, useTheme, useViewport } from "./core";
export type CardPushProps = {
/** Where the outgoing card leaves to. The incoming card rises from the opposite side. */
direction?: "up" | "down" | "left" | "right";
/** Degrees the outgoing card tilts as it leaves. */
tilt?: number;
/** Canvas behind the cards. Defaults to a tone between the theme background and foreground. */
backdrop?: string;
/** Card outline. Defaults to the theme border. */
borderColor?: string;
};
/** Scale at which a scene reads as a card. */
const CARD = 0.8;
function CardPushPresentation({
children,
presentationDirection,
presentationProgress,
passedProps,
}: TransitionPresentationComponentProps<CardPushProps>) {
const theme = useTheme();
const { u } = useViewport();
const direction = passedProps.direction ?? "up";
const vertical = direction === "up" || direction === "down";
const sign = direction === "up" || direction === "left" ? -1 : 1;
const radius = u(theme.radius * 1.6);
const outline = passedProps.borderColor ?? theme.colors.border;
const at = (start: number, end: number, easing: (t: number) => number) =>
interpolate(presentationProgress, [start, end], [0, 1], { ...CLAMP, easing });
const move = (percent: number) => (vertical ? `0 ${percent}%` : `${percent}% 0`);
// Rounded corners, hairline and shadow, faded in by `amount`.
const card = (amount: number): React.CSSProperties => ({
overflow: "hidden",
borderRadius: radius * amount,
outline: `1px solid ${alpha(outline, amount)}`,
boxShadow: `0 ${u(30)}px ${u(90)}px ${alpha("#000000", 0.4 * amount)}`,
});
if (presentationDirection === "exiting") {
const shrink = at(0, 0.45, easings.gentle);
const leave = at(0.3, 0.9, exitEasing);
// zIndex lifts the outgoing card above the incoming scene, so the next card waits behind it.
return (
<AbsoluteFill style={{ zIndex: 1 }}>
<AbsoluteFill
style={{
...card(shrink),
scale: String(1 - (1 - CARD) * shrink),
translate: move(sign * leave * 115),
rotate: `${sign * (passedProps.tilt ?? 6) * leave}deg`,
}}
>
{children}
</AbsoluteFill>
</AbsoluteFill>
);
}
const rise = at(0.2, 0.75, easings.smooth);
const grow = at(0.55, 1, easings.gentle);
const backdrop =
passedProps.backdrop ?? `color-mix(in srgb, ${theme.colors.foreground} 14%, ${theme.colors.background})`;
return (
<AbsoluteFill style={{ background: grow < 1 ? backdrop : undefined }}>
<AbsoluteFill
style={{
...card(1 - grow),
scale: String(CARD - 0.08 * (1 - rise) + (1 - CARD) * grow),
translate: move(-sign * (1 - rise) * 14),
}}
>
{children}
<AbsoluteFill style={{ background: "#000000", opacity: 0.45 * (1 - rise) }} />
</AbsoluteFill>
</AbsoluteFill>
);
}
export function cardPush(props: CardPushProps = {}): TransitionPresentation<CardPushProps> {
return { component: CardPushPresentation, props };
}