Guía Maestra de Remotion — Creación Programática de Videos en React
Remotion permite la creación de videos cinematográficos con código React puro. Cada fotograma del video se trata como un estado determinista (frame) => UI, generando visuales perfectos a 60 FPS sin tirones ni caídas de fotogramas.
1. Arquitectura de Composición y Configuración Raíz
A. Punto de Entrada Raíz (Root.tsx)
import { Composition } from 'remotion';
import { MainExplainer } from './compositions/MainExplainer';
import { VerticalShort } from './compositions/VerticalShort';
export const RemotionRoot: React.FC = () => {
return (
<>
{/* Explicativo Cinemático 16:9 */}
<Composition
id="MainExplainer-1080p"
component={MainExplainer}
durationInFrames={30 * 32} // 32 segundos @ 30 FPS = 960 fotogramas
fps={30}
width={1920}
height={1080}
defaultProps={{
title: 'Misterio del Derretimiento de los Picos de Hielo del Mundo',
author: 'NARA Studio',
themeColor: '#f97316',
}}
/>
{/* Vertical 9:16 para TikTok / Reels / Shorts */}
<Composition
id="VerticalShort-60fps"
component={VerticalShort}
durationInFrames={60 * 15} // 15 segundos @ 60 FPS = 900 fotogramas
fps={60}
width={1080}
height={1920}
/>
</>
);
};
B. Reglas de Duración y FPS
- 16:9 YouTube / Explicativo: Recomendación
30 fps(equilibrio entre velocidad de renderizado y suavidad de movimiento) o60 fpspara tipografía cinética ultra nítida. - Duración en Fotogramas: Siempre calcula
durationInFrames = Math.round(seconds * fps). - Sin Tirones: No uses
setIntervalosetTimeout. Usa siempreuseCurrentFrame()yuseVideoConfig().
2. Primitivas de Diseño y Línea de Tiempo
AbsoluteFill
Base de toda la capa de la escena. Automáticamente position: absolute; inset: 0; width: 100%; height: 100%;:
import { AbsoluteFill } from 'remotion';
export const SceneBackground: React.FC = () => {
return (
<AbsoluteFill style={{ backgroundColor: '#090812', overflow: 'hidden' }}>
{/* Contenido visual */}
</AbsoluteFill>
);
};
Sequence vs Series
Sequence: Coloca elementos en un tiempo absoluto (fromydurationInFrames). Ideal para audio de fondo, marcas de agua, superposiciones HUD o elementos que se superponen entre sí.Series: Ordena las escenas secuencialmente de forma automática sin necesidad de calcular el desplazamiento de fotogramas manualmente.
import { Series, Sequence, Audio, staticFile } from 'remotion';
export const VideoStory: React.FC = () => {
return (
<AbsoluteFill>
{/* Audio de Música de Fondo a lo Largo del Video */}
<Audio src={staticFile('audio/ambient_score.mp3')} volume={0.35} />
{/* Secuencia Automática de Escenas */}
<Series>
<Series.Sequence durationInFrames={120}>
<HookScene />
</Series.Sequence>
<Series.Sequence durationInFrames={180}>
<ProblemScene />
</Series.Sequence>
<Series.Sequence durationInFrames={240}>
<SolutionScene />
</Series.Sequence>
<Series.Sequence durationInFrames={150}>
<CallToActionScene />
</Series.Sequence>
</Series>
</AbsoluteFill>
);
};
3. Física de Animación: spring() e interpolate()
A. Fórmula de Resorte Natural (spring())
Usa spring() para generar movimientos de física real (elimina la rigidez típica de "PowerPoint"):
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 1. Pop Rápido (Texto/Insignia Aparece con Impacto)
const popScale = spring({
frame,
fps,
config: { damping: 10, mass: 0.4, stiffness: 180 },
});
// 2. Desplazamiento Suave (Cámara se Desliza con Suavidad)
const smoothSlide = spring({
frame,
fps,
config: { damping: 20, mass: 1.2, stiffness: 60 },
});
// 3. Caída con Rebote (Icono/Partícula Cae y Rebotan)
const bounce = spring({
frame: frame - 10, // retraso de 10 fotogramas
fps,
config: { damping: 8, mass: 0.6, stiffness: 140 },
});
B. Mapeo de Valores Suave (interpolate())
import { interpolate, Easing } from 'remotion';
// Transición de Alpha y Rotación con Easing Bezier
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: 'clamp',
});
const rotateY = interpolate(frame, [0, 60], [-45, 0], {
easing: Easing.bezier(0.16, 1, 0.3, 1),
extrapolateRight: 'clamp',
});
4. Tipografía Cinética y Sincronización con Voz en Off
A. Revelación de Palabras Escalonada (Texto Aparece Palabra por Palabra)
import React from 'react';
import { useCurrentFrame, useVideoConfig, spring, interpolate } from 'remotion';
interface KineticTextProps {
sentence: string;
startFrame?: number;
wordDelay?: number;
}
export const KineticText: React.FC<KineticTextProps> = ({
sentence,
startFrame = 0,
wordDelay = 4, // 4 fotogramas de separación entre palabras
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const words = sentence.split(' ');
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', justifyContent: 'center' }}>
{words.map((word, idx) => {
const wordStart = startFrame + idx * wordDelay;
const progress = spring({
frame: frame - wordStart,
fps,
config: { damping: 12, mass: 0.4, stiffness: 160 },
});
const translateY = interpolate(progress, [0, 1], [40, 0]);
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<span
key={idx}
style={{
display: 'inline-block',
transform: `translateY(${translateY}px)`,
opacity,
fontSize: '48px',
fontWeight: 800,
color: idx === 0 ? '#f97316' : '#ffffff',
letterSpacing: '-1px',
fontFamily: "'JetBrains Mono', sans-serif",
}}
>
{word}
</span>
);
})}
</div>
);
};
B. Sincronización de Voz en Off y Efectos de Sonido
- Coloca los archivos de audio en la carpeta
public/audio/. - Usa
staticFile('audio/file.mp3'). - Crea transiciones de audio de entrada/salida usando la propiedad
volume:
<Audio
src={staticFile('audio/naskah_vo.mp3')}
volume={(f) =>
interpolate(f, [0, 15, 880, 900], [0, 1, 1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
})
}
/>
5. Activos Asíncronos y Precarga (delayRender)
Cuando el video carga fuentes personalizadas, imágenes externas o datos de API, Remotion DEBE ser notificado para no renderizar fotogramas vacíos:
import { delayRender, continueRender } from 'remotion';
import { useEffect, useState } from 'react';
export const DynamicScene: React.FC = () => {
const [handle] = useState(() => delayRender('Cargando Fuentes y Activos'));
useEffect(() => {
document.fonts.ready.then(() => {
continueRender(handle);
});
}, [handle]);
return <AbsoluteFill>...</AbsoluteFill>;
};
6. Plantillas y Recetas de Motion Graphics NARA
Fórmula de 8 Etapas Cinematográficas (Anti-PPT)
- Escena 1: Gancho Frío (00-03s) $ ightarrow$ Misterio visual + Tipografía cinética grande.
- Escena 2: Problema Expuesto (04-07s) $ ightarrow$ Visual de hielo derrumbándose / gráfico rojo ardiente.
- Escena 3: Inmersión Profunda en Datos (08-12s) $
ightarrow$ Números estadísticos que cuentan hacia arriba (
Math.floor). - Escena 4: Cambio de Escala Dinámico (13-16s) $ ightarrow$ Cambio de escala macro a micro.
- Escena 5: Impacto Humano (17-20s) $ ightarrow$ Personaje de dibujos animados reacciona en el lienzo.
- Escena 6: Revelación de la Solución (21-25s) $ ightarrow$ Visual cambia a colores verde/naranja brillante.
- Escena 7: Pasos de Acción (26-29s) $ ightarrow$ 3 puntos en tarjetas flotantes 3D.
- Escena 8: Clímax y Llamada a la Acción (30-32s) $ ightarrow$ Logotipo del estudio palpita + audio whoosh de caída.
7. Ejecución de Renderizado desde CLI y Aceleración por Hardware
A. Renderizar un Solo Video (1080p 60fps)
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --gl=angle
B. Renderizado Rápido con Alta Concurrencia
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --concurrency=8 --gl=angle --jpeg-quality=90
C. Banderas de Renderizado Importantes
--gl=angle: Utiliza aceleración de GPU DirectX en Windows (mucho más rápido que el rasterizador por software).--gl=swiftshader: Retroceso por software si no hay GPU disponible en contenedor/CI.--concurrency=N: Número de núcleos de CPU utilizados para renderizado paralelo.--muted: Renderiza sin flujo de audio si solo se necesita el visual.--props='{"title":"Nuevo"}': Pasa parámetros dinámicos a través de JSON en la línea de comandos.
8. Guía de Solución de Problemas
- Pantalla Negra / Imágenes que No Aparecen: Asegúrate de que los activos estén en la carpeta
public/y se llamen constaticFile('nombre_archivo'). - Video se Congela Durante el Renderizado: Verifica si hay un bucle infinito en
useMemoo un componente React que no cierradelayRender. - Audio Desincronizado: Asegúrate de que el valor
fpsde la composición coincida exactamente con el cálculo de los segundos de la voz en off (segundos = fotogramas / fps).