O que Remotion Compose faz?
Você transforma um briefing em linguagem natural em uma composição Remotion completa e pronta para visualização - React/TypeScript real com animação baseada em quadros. A saída é um arquivo .tsx em src/compositions/, registrado em src/Root.tsx, que o usuário pode imediatamente navegar no Studio ou entregar ao remotion-render.
Ative em qualquer solicitação para criar, gerar, animar ou "escrever o código do vídeo para" uma demonstração de produto, walkthrough, destaque de funcionalidade ou animação de marketing - incluindo "crie um vídeo de 20 segundos mostrando X."
Etapa 1 - Extrair o briefing
Não comece a codificar até ter estes itens. Pergunte em um lote compacto; preencha lacunas óbvias com padrões sensatos em vez de parar:
- Duração em segundos e fps (padrão 30 - o padrão para vídeo de produto e marketing, e metade do custo de renderização de 60 fps. Use 60fps apenas ao animar movimento fino de cursor ou UI de rolagem onde 30 trava visivelmente).
- Cenas - o que acontece, em ordem. Insista por uma lista de batidas: "logo, depois três funcionalidades, depois um CTA" é melhor que "uma demonstração do meu app."
- Marca - cores primária/destaque/fundo em hex, e a fonte (um nome de família
@remotion/google-fonts). - Proporção / plataforma - 16:9 paisagem (1920×1080), 9:16 retrato (1080×1920) para redes sociais, ou 1:1 quadrado (1080×1080).
- Texto - os títulos/nomes de funcionalidades/texto do CTA reais. Nunca invente alegações de produto; peça as palavras reais.
Converta segundos em quadros em todos os lugares: durationInFrames = seconds * fps.
Durações totais típicas: 15-60s para uma demonstração social/de produto, 6-15s para um corte de anúncio.
Etapa 2 - Construir um plano de cenas
Uma <Composition> por vídeo, um componente por cena, costurados na linha do tempo com <Sequence>. Defina o orçamento de quadros antes de escrever código:
Total: 20s @ 30fps = 600 quadros
Intro (logo) quadros 0-90 (Sequence from=0 durationInFrames=90)
Funcionalidade 1 quadros 90-240 (Sequence from=90 durationInFrames=150)
Funcionalidade 2 quadros 240-390 (Sequence from=240 durationInFrames=150)
Funcionalidade 3 quadros 390-540 (Sequence from=390 durationInFrames=150)
CTA final quadros 540-600 (Sequence from=540 durationInFrames=60)
Limites de ritmo ao orçar:
- Dê a qualquer batida que carregue texto pelo menos 2 segundos (60 quadros a 30fps) - animação de entrada mais pausa. Um título precisa de aproximadamente 150-200ms de pausa por palavra para ser legível; um título de 6 palavras quer ~1s de pausa após chegar.
- Animações de entrada: 10-20 quadros. Cross-fades e saídas: 12-15 quadros. Mais longo parece lento, mais curto parece uma falha.
- Nenhuma cena com menos de ~45 quadros, a menos que seja um corte rápido intencional.
<Sequence from={F}> desloca o relógio local do filho para que dentro desse componente useCurrentFrame() comece em 0. Anime cada cena como se começasse no quadro 0.
Etapa 3 - Referência da API principal
useCurrentFrame()- o quadro atual (o relógio). Tudo anima em função disso.useVideoConfig()-{ fps, width, height, durationInFrames }.interpolate(frame, [inFrames], [outValues], opts)- mapeamento linear. Sempre passe{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }para entrada/saída para que os valores não ultrapassem seu intervalo.spring({ frame, fps, config })- 0→1 baseado em física (levemente overshoot). Use para qualquer coisa que deva parecer viva (pops, slides, scale-ins).AbsoluteFill- um div posicionado absolutamente em tela cheia; a camada base de cada cena.Sequence- desloca o tempo e limita o tempo dos filhos (o agendador de cenas).Series/Series.Sequence- cenas consecutivas sem calcular deslocamentosfrommanualmente.Img,staticFile- veja a Etapa 6.
Etapa 4 - spring() vs interpolate()
Use spring() quando… | Use interpolate() quando… |
|---|---|
| O movimento deve parecer físico - pops, saltos, scale-ins, slide-ins | Você precisa de controle preciso e linear entre quadros exatos |
| Você quer um assentamento natural/overshoot | Fades (opacidade 0→1), mudanças de cor, barras de progresso, qualquer coisa sincronizada com o quadro |
| Entradas de cards, logos, badges | Cross-fades e saídas onde overshoot pareceria errado |
Padrão - dirija uma transformação com spring, uma opacidade com interpolate:
const { fps } = useVideoConfig();
const frame = useCurrentFrame();
const pop = spring({ frame, fps, config: { damping: 200 } }); // 0 → 1, vivo
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
const scale = interpolate(pop, [0, 1], [0.8, 1]);
damping: 200 é a configuração de trabalho - um assentamento rápido com overshoot quase imperceptível. Damping menor (~10-20) salta visivelmente; use apenas quando salto for o objetivo.
Etapa 5 - Estrutura padrão de demonstração de produto
Codifique esta forma, a menos que o briefing diga o contrário: intro logo/marca → N destaques de funcionalidades → CTA final. Torne-o orientado a dados (Etapa 6) para que variações sejam gratuitas.
// src/compositions/ProductDemo.tsx
import {
AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig,
interpolate, spring,
} from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont();
export type Feature = { title: string; body: string };
export type ProductDemoProps = {
brand: string;
colors: { bg: string; fg: string; accent: string };
logoText: string;
features: Feature[];
cta: string;
};
const Intro: React.FC<{ text: string; colors: ProductDemoProps["colors"] }> = ({ text, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const s = spring({ frame, fps, config: { damping: 200 } });
const scale = interpolate(s, [0, 1], [0.7, 1]);
const opacity = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", alignItems: "center" }}>
<h1 style={{ fontFamily, fontSize: 120, color: colors.fg, transform: `scale(${scale})`, opacity }}>
{text}
</h1>
</AbsoluteFill>
);
};
const FeatureCallout: React.FC<{ feature: Feature; colors: ProductDemoProps["colors"] }> = ({ feature, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const slide = spring({ frame, fps, config: { damping: 200 } });
const x = interpolate(slide, [0, 1], [-60, 0]);
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", paddingLeft: 160 }}>
<div style={{ transform: `translateX(${x}px)`, opacity }}>
<div style={{ fontFamily, fontSize: 32, color: colors.accent, fontWeight: 700, letterSpacing: 2 }}>
{feature.title.toUpperCase()}
</div>
<div style={{ fontFamily, fontSize: 64, color: colors.fg, maxWidth: 1100, marginTop: 16 }}>
{feature.body}
</div>
</div>
</AbsoluteFill>
);
};
const Outro: React.FC<{ cta: string; colors: ProductDemoProps["colors"] }> = ({ cta, colors }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 18], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.accent, justifyContent: "center", alignItems: "center" }}>
<h2 style={{ fontFamily, fontSize: 84, color: colors.bg, opacity }}>{cta}</h2>
</AbsoluteFill>
);
};
export const ProductDemo: React.FC<ProductDemoProps> = ({ colors, logoText, features, cta }) => {
const intro = 90;
const per = 150;
const outro = 60;
return (
<AbsoluteFill>
<Sequence durationInFrames={intro}>
<Intro text={logoText} colors={colors} />
</Sequence>
{features.map((f, i) => (
<Sequence key={i} from={intro + i * per} durationInFrames={per}>
<FeatureCallout feature={f} colors={colors} />
</Sequence>
))}
<Sequence from={intro + features.length * per} durationInFrames={outro}>
<Outro cta={cta} colors={colors} />
</Sequence>
</AbsoluteFill>
);
};
Registre em src/Root.tsx, calculando durationInFrames a partir dos mesmos números e fornecendo defaultProps:
<Composition
id="ProductDemo"
component={ProductDemo}
durationInFrames={90 + 3 * 150 + 60}
fps={30}
width={1920}
height={1080}
defaultProps={{
brand: "Acme",
colors: { bg: "#0B0B0F", fg: "#FFFFFF", accent: "#6C5CE7" },
logoText: "Acme",
features: [
{ title: "Rápido", body: "Entregue em segundos, não em sprints." },
{ title: "Simples", body: "Um prompt, um vídeo." },
{ title: "Seu", body: "Sua marca, seu texto." },
],
cta: "Comece grátis →",
}}
/>
Para retrato/redes sociais, defina width={1080} height={1920}; para quadrado, 1080×1080.
Mantenha os componentes idênticos - apenas a tela muda.
Etapa 6 - Props orientadas a dados e assets estáticos
A composição acima é totalmente orientada a dados: cada palavra e cor é uma prop. Esse é o ponto - variações (novo texto, nova paleta, texto localizado) não precisam de nova solicitação; apenas passe props diferentes (remotion-render faz isso a partir de um arquivo JSON para lotes).
Assets estáticos (logos, capturas de tela, áudio) ficam em public/assets/ e são referenciados com staticFile() - nunca uma importação ou um caminho bruto:
import { Img, staticFile } from "remotion";
<Img src={staticFile("assets/logo.png")} />
Etapa 7 - Saída
Salve o arquivo final em src/compositions/<NomeDoVideo>.tsx, registre em src/Root.tsx, e diga ao usuário para visualizar com npx remotion studio (http://localhost:3000). Então entregue: "Navegue no Studio. Diga-me o que mudar - muito rápido, cor errada, texto diferente - e eu edito e re-renderizo (remotion-render)."
NÃO FAÇA
- NÃO anime com transições/animações CSS ou
setTimeout/setInterval. O Remotion renderiza quadros de forma independente e em paralelo; qualquer coisa não derivada deuseCurrentFrame()é invisível ou inconsistente na renderização final, mesmo que pareça boa na prévia do navegador. - NÃO use
Math.random()para efeitos de dispersão/partículas - cada worker de renderização obtém valores diferentes e os quadros param de corresponder. Userandom(seed)do Remotion para pseudo-aleatoriedade determinística. - NÃO busque dados ou espere por trabalho assíncrono sem
delayRender()/continueRender()- o renderizador captura o quadro antes dos dados chegarem. - NÃO codifique fps, width ou height dentro de componentes de cena - leia-os de
useVideoConfig()para que alternar entre 30↔60fps ou paisagem↔retrato não quebre cada temporização e layout. - NÃO omita
extrapolateLeft/Right: "clamp"em interpolações de entrada/saída - a opacidade sobe além de 1 e elementos saem da tela após seu intervalo terminar. - NÃO invente títulos, alegações de funcionalidades ou CTAs - use o texto real do usuário, e peça se estiver faltando.
Padrão de qualidade
- Cada valor animado é uma função pura de
useCurrentFrame()(mais props) - navegar para trás no Studio parece idêntico a avançar. - O orçamento de quadros no plano soma exatamente a
durationInFramesemRoot.tsx, e nenhuma batida de texto segura por menos de seu mínimo legível. - Todo texto e cores chegam via props tipadas com
defaultPropsfornecidos - sem literais enterrados em componentes de cena. - Assets carregam via
staticFile(); o arquivo registra-se limpo e pré-visualiza no Studio sem erros no console.