Communitygithub.com

Guilhermeslvg/video-portfolio

Mejores prácticas para contenido, animación y efectos

¿Qué es video-portfolio?

Esta habilidad proporciona una guía completa para crear videos programáticos con Remotion, centrándose en contenido, animación y efectos visuales. Explica cómo preservar los cambios del usuario, estructurar composiciones y escenas, y aplicar animaciones fluidas utilizando `useCurrentFrame()`, `interpolate()` y `Easing` en lugar de CSS `transition` o `animation`. También cubre el uso de componentes interactivos con `Interactive.withSchema()` para escenas editables, el manejo de activos multimedia (`<Video>`, `<Audio>`, `<CanvasImage>`, `<AnimatedImage>`) y la optimización del rendimiento con `premountFor`. Se incluyen ejemplos completos de código, así como referencias a guías avanzadas para efectos, transiciones, mapas, voz en off, subtítulos y composiciones conectadas. Compatible con plataformas que ejecutan Remotion (Studio, Node.js, Lambda), esta habilidad ayuda a los desarrolladores a aprovechar al máximo el ecosistema de Remotion para crear videos interactivos de alta calidad.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Guilhermeslvg/video-portfolio/tree/HEAD/.agents/skills/remotion-markup

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Vista previa

Del README del skill

Documentación

Esta es una guía para escribir marcado React de Remotion. Si esto no es relevante, carga en su lugar las Mejores Prácticas de Remotion.

Preservar los cambios del usuario

Los usuarios pueden hacer ediciones en el código fuera de la conversación.

Si detectas un cambio sorprendente realizado mientras tanto, no lo sobrescribas, asume que fue intencional o pide confirmación.

Reglas generales

Dentro de una composición o componente de escena, obtén fps de useVideoConfig() para todos los cálculos de segundos a fotogramas, incluyendo props de tiempo como from, trimBefore, durationInFrames y premountFor. Mantener la expresión en el nodo JSX, como trimBefore={4 * fps}, permite que Studio edite el tiempo y lo mantenga correcto si la frecuencia de fotogramas de la composición cambia.

Impulsa las animaciones usando useCurrentFrame() e interpolate(). Las transiciones CSS (transition) o animaciones (animation) no se renderizarán correctamente; deben ser refactorizadas. Las clases de animación de Tailwind no se renderizarán correctamente; deben ser refactorizadas.

Usa Easing.bezier() y Easing.spring() para personalizar el tiempo.

Estructura tu marcado de acuerdo con las Mejores Prácticas de Interactividad de Remotion. Prefiere Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) para escenas con props editables. Su marcado interno puede devolver un fragmento; el wrapper maneja el diseño y los estilos de instancia. Elementos con tamaño, como los tercios inferiores, pueden mantener el diseño predeterminado y pasar style a su propia raíz. Registra escenas reutilizables como composiciones conectadas. Coloca el tiempo directamente en los componentes que lo soportan; evita wrappers <Sequence> redundantes. Otorga un segundo de premontaje a cada componente temporizado que soporte premountFor: premountFor={fps}, donde fps proviene de useVideoConfig(). Aplica esto a medios, componentes interactivos, <Sequence>, <TransitionSeries.Sequence> y <TransitionSeries.Overlay>, incluyendo componentes temporizados anidados dentro de escenas. Premonta también el elemento padre de la línea de tiempo cuando un elemento anidado necesita montarse antes de que el padre comience. Un componente sin premountFor, como <TransitionSeries.Transition>, no necesita sustituto.

Studio edita el nodo fuente JSX que creó un elemento. Crea cada registro de composición, clip, escena, capa y secuencia que deba ser editable de forma independiente como su propio nodo JSX, con sus props editables en línea. Los bucles programáticos son adecuados cuando las instancias generadas se controlan intencionalmente como una plantilla fuente única, no cuando los usuarios necesitan editar las instancias por separado.

import { useCurrentFrame, useVideoConfig, Easing, interpolate, Interactive } from "remotion";

export const FadeIn = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  return (
    <Interactive.Div
      name="Title"
      premountFor={fps}
      style={{
        opacity: interpolate(frame, [0, 2 * fps], [0, 1], {
          extrapolateRight: "clamp",
          extrapolateLeft: "clamp",
          easing: Easing.bezier(0.16, 1, 0.3, 1),
        }),
      }}
    >
      Hello World!
    </Interactive.Div>
  );
};

Mantén la llamada a interpolate() en línea dentro de la prop style. Usa las propiedades CSS scale, translate, rotate en lugar de transform.

// 👍 Fotogramas clave editables en línea y abreviaturas de transformación
style={{
  scale: interpolate(frame, [0, 100], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
    output: 'perceptual-scale' // Para animaciones de `scale`, usa "output: 'perceptual-scale'"
  }),
  translate: interpolate(frame, [0, 100], ["0px 0px", "100px 100px"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
  rotate: interpolate(frame, [0, 100], ["20deg", "90deg"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
}}

// 👎 Valores no en línea y cadenas transform se vuelven más difíciles de editar en Studio
const scale = interpolate(frame, [0, 100], [0, 1]);

style={{
  transform: `scale(${scale})`,
}}

Activos

Coloca los activos en la carpeta public/ en la raíz de tu proyecto. Usa staticFile() para hacer referencia a archivos de la carpeta public/.

Componentes multimedia

Agrega video y audio usando <Video> y <Audio> de @remotion/media. Agrega imágenes usando el componente <CanvasImage>. Agrega GIFs animados, imágenes APNG, WebP o AVIF usando <AnimatedImage>, usa @remotion/gif si no estás en Chrome. Usa staticFile() para archivos en public/ o pasa una URL remota directamente:

import { Audio, Video } from "@remotion/media";
import { staticFile, CanvasImage, AnimatedImage, useVideoConfig } from "remotion";

export const MyComposition = () => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video src={staticFile("video.mp4")} premountFor={fps} style={{ opacity: 0.5 }} />
      <Audio src={staticFile("audio.mp3")} premountFor={fps} />
      <CanvasImage
        src={staticFile("logo.png")}
        premountFor={fps}
        style={{ width: 100, height: 100 }}
      />
      <Video src="https://remotion.media/video.mp4" premountFor={fps} />
      <AnimatedImage src={staticFile('nyancat.gif')} premountFor={fps} />
    </>
  );
};

Si la composición es principalmente una línea de tiempo de clips de video o audio, lee video-editing.md antes de elegir su estructura fuente.

Escena de ejemplo

Un video de fondo con un tercio inferior. El tercio inferior es un componente interactivo con su propia línea de tiempo, registrado como una composición conectada. Su texto se pasa como children y accentColor es una prop editable. La aparición gradual está animada con fotogramas clave en línea en el sitio de llamada.

// MyScene.tsx
import { Video } from "@remotion/media";
import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
import { LowerThird } from "./LowerThird";

export const MyScene = () => {
  const { fps } = useVideoConfig();
  const frame = useCurrentFrame();

  return (
    <>
      <Video
        name="Background"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{
          position: "absolute",
          width: "100%",
          height: "100%",
        }}
      />
      <LowerThird
        name="Lower third"
        from={1 * fps}
        premountFor={fps}
        accentColor="#0b84f3"
        style={{
          opacity: interpolate(frame, [1 * fps, 2 * fps], [0, 1], {
            extrapolateRight: "clamp",
            extrapolateLeft: "clamp",
            easing: Easing.bezier(0.16, 1, 0.3, 1),
          }),
        }}
      >
        Jane Doe, Product Designer
      </LowerThird>
    </>
  );
};
// LowerThird.tsx
import type React from "react";
import { Interactive, type InteractivitySchema } from "remotion";

type LowerThirdProps = {
  readonly children: React.ReactNode;
  readonly accentColor: string;
  readonly style?: React.CSSProperties;
};

const LowerThirdInner: React.FC<LowerThirdProps> = ({
  children,
  accentColor,
  style,
}) => {
  return (
    <Interactive.Div
      style={{
        position: "absolute",
        left: 80,
        bottom: 80,
        display: "flex",
        alignItems: "center",
        gap: 20,
        backgroundColor: "white",
        borderRadius: 16,
        padding: "20px 32px",
        color: "black",
        fontFamily: "Helvetica, Arial, sans-serif",
        fontSize: 48,
        fontWeight: 600,
        ...style,
      }}
    >
      <div
        style={{
          width: 8,
          alignSelf: "stretch",
          borderRadius: 4,
          backgroundColor: accentColor,
        }}
      />
      {children}
    </Interactive.Div>
  );
};

const lowerThirdSchema = {
  ...Interactive.childrenSchema,
  accentColor: {
    type: "color",
    default: "#0b84f3",
    description: "Color de acento",
  },
} as const satisfies InteractivitySchema;

export const LowerThird = Interactive.withSchema({
  Component: LowerThirdInner,
  componentName: "LowerThird",
  schema: lowerThirdSchema,
  wrapInSequence: true,
});
import { Composition } from "remotion";
import { LowerThird } from "./LowerThird";
import { MyScene } from "./MyScene";

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="MyScene"
        component={MyScene}
        durationInFrames={60}
        fps={30}
        width={1280}
        height={720}
      />
      <Composition
        id="LowerThird"
        component={LowerThird}
        durationInFrames={30}
        fps={30}
        width={1280}
        height={720}
        defaultProps={{
          children: "Jane Doe, Product Designer",
          accentColor: "#0b84f3",
        }}
      />
    </>
  );
};

Retraso, recorte

Coloca el tiempo directamente en los componentes que lo soportan. Consulta Props de tiempo para las props soportadas, ejemplos y orden de operaciones.

Mapas

Consulta Mapas de Remotion si deseas incluir mapas en el video.

Resaltados de texto y anotaciones

Consulta resaltados-de-texto.md para resaltados de texto (marcadores de resaltado), círculos, subrayados, tachados, texto tachado y cuadros.

Videos de múltiples escenas

Consulta video-multi-escena.md si planeas hacer un video con múltiples escenas consecutivas.

Composiciones conectadas

Cuando una escena o grupo de capas merece su propia línea de tiempo editable, sigue composiciones-conectadas.md. Prefiere esta estructura para escenas sustanciales en un video de múltiples escenas.

Acción de pre-componer

Para una solicitud de Studio como Pre-componer Resplandor ambiental (src/BarChart.tsx:134), encuentra el marcado de secuencia seleccionado en la ubicación dada. Crea una composición conectada, siguiendo composiciones-conectadas.md: extrae el marcado en un componente con nombre, preferiblemente hazlo interactivo con Interactive.withSchema({wrapInSequence: true}), y registra la misma referencia de componente exportada con un <Composition> único en la raíz. Renderiza el componente interactivo directamente con sus props de tiempo, o como el único hijo de una secuencia cuando ese wrapper tenga un propósito. Si el nodo seleccionado ya es una secuencia, conserva sus props y extrae sus hijos. El registro necesita dimensiones, fps, duración y defaultProps equivalentes a su uso padre. Una extracción de componente sin una composición registrada no completa una solicitud de pre-composición.

Lleva los estilos heredados como fontFamily y la carga de fuentes al componente extraído; consulta independencia-del-padre.

Voz en off

Consulta voz-en-off.md para agregar una voz en off generada por IA a las composiciones de Remotion usando ElevenLabs TTS.

Incrustación de videos

Consulta incrustación-de-videos.md para conocimiento avanzado sobre incrustación de videos: recorte, volumen, velocidad, bucle, tono.

Incrustación de audio

Consulta audio.md para características avanzadas de audio como recorte, volumen, velocidad, tono.

Recorte

Consulta recorte.md si necesitas recortar el rectángulo visible de un componente.

Transiciones

Consulta transiciones.md para patrones de transición de escenas.

Desenfoque de movimiento

Al agregar desenfoque de movimiento o una estela de movimiento, lee desenfoque-de-movimiento.md para el enfoque preferido de HTML en canvas, requisitos de vista previa y alternativas.

Efectos visuales y de píxeles

Al agregar un efecto visual, primero identifica el elemento o grupo al que debe afectar. Si el componente objetivo soporta effects y un efecto incorporado a continuación coincide con la solicitud, prefiere ese efecto sobre una aproximación CSS. Por ejemplo, aplica vignette() directamente a la prop effects de un <Video> cuando el viñeteado debe afectar ese video.

Estos componentes aceptan effects directamente:

Importar desdeComponentes
remotion<Img>, <CanvasImage>, <AnimatedImage>, <Solid>, <HtmlInCanvas>
@remotion/media<Video>
@remotion/gif<Gif>
@remotion/rive<RemotionRiveCanvas>
@remotion/shapes<Arrow>, <Callout>, <Circle>, <Ellipse>, <Heart>, <Pie>, <Polygon>, <Rect>, <Spark>, <Star>, <Triangle>

Los elementos HTML ordinarios, los wrappers DOM <Interactive.*>, <AbsoluteFill> y <Sequence> no aceptan effects directamente. Para aplicar efectos a su contenido, envuélvelos en <HtmlInCanvas> y coloca la prop effects en el wrapper.

Para otros elementos, prefiere HTML y CSS regulares cuando sea adecuado. Considera envolver un grupo en <HtmlInCanvas> cuando el posprocesamiento de todo el grupo sea apropiado y el entorno lo soporte.

Lee efectos.md antes de aplicar un efecto para importaciones, parámetros y configuración. También cubre efectos personalizados reutilizables con createEffect() cuando ningún efecto incorporado coincida.

Efectos disponibles

Efectos incorporados de @remotion/effects:

  • Color y tono: brightness(), colorCorrection(), colorKey(), contrast(), duotone(), exposure(), grayscale(), hue(), invert(), levels(), linearGradient(), linearGradientTint(), lut(), saturation(), shadowsHighlights(), thermalVision(), tint(), vibrance(), whiteBalance().
  • Desenfoque y estelas: blur(), lightTrail(), linearProgressiveBlur(), radialProgressiveBlur(), `regionBlur …

Individual skills in this repo

This repo contains 5 individual skills — each has its own dedicated page.

Skills relacionados