Communitygithub.com

iamhuynq/video-remotion

Estructura el marcado de Remotion para hacerlo interactivo en Remotion Studio.

¿Qué es video-remotion?

Esta skill define las mejores prácticas para estructurar el marcado de Remotion de manera que Remotion Studio pueda reconocer la estructura del código y habilitar la interactividad. Permite que los elementos sean seleccionables mediante clics, arrastrables, redimensionables y rotables, además de permitir la edición de estilos CSS, keyframes y valores de easing. Si el marcado es demasiado complejo, los valores se muestran atenuados. La skill recomienda el uso de componentes interactivos con líneas de tiempo propias, el registro de componentes reutilizables como composiciones conectadas, el uso de `Interactive.create()` para la interactividad y la escritura de estilos y animaciones inline con `interpolate()`. También cubre cómo exponer campos de esquema para la edición en Studio, el uso de nodos JSX separados para cada elemento editablemente independiente y la creación de paths SVG editables con `Interactive.Path`. Soporta Remotion Studio y está diseñada para agentes de IA como Cursor o Codex que generan código de Remotion.

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

Preguntar en tu IA favorita

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

Vista previa

Del README del skill

video-remotion preview

Documentación

Al escribir el marcado de Remotion de una manera específica, Remotion Studio puede reconocer la estructura del código y hacerlo interactivo:

  • Permite seleccionar elementos haciendo clic en ellos.
  • Permite arrastrar y soltar, redimensionar y rotar.
  • Editar los estilos CSS.
  • Hacer editables los keyframes y los valores de easing.

Si el marcado es demasiado complejo para que Studio lo haga interactivo, los valores se muestran atenuados.

Prefiere componentes interactivos con sus propias líneas de tiempo

Construye nuevas composiciones con Track, composiciones conectadas e Interactive.create().

Establece layout explícitamente como 'absolute-fill' para escenas o 'none' para elementos delimitados. Registra los mismos componentes de escena exportados y componentes de elementos reutilizables con Composition.

Comienza con el ejemplo completo de Track + composiciones conectadas. Incluye el elemento, la escena, la línea de tiempo principal y los registros raíz.

import {Track, useVideoConfig} from 'remotion';
import {Chapter} from './Chapter';

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

  return (
    <Track name="Capítulos">
      <Chapter
        name="Apertura"
        durationInFrames={3 * fps}
        title="Introducción"
      />
      <Chapter
        name="Característica"
        from={3 * fps}
        durationInFrames={4 * fps}
        title="Nuevas funciones"
      />
    </Track>
  );
};

Coloca los estilos de fondo y alineación en cada instancia de escena, como se muestra en el ejemplo completo. Mantén los contenedores solo de diseño como HTML simple.

Elegir campos de esquema

Cada clave en el esquema es una prop que Studio puede leer y editar en el lugar de llamada. Las props que no están en el esquema aún funcionan, pero no son editables en Studio. Las claves pueden usar notación de puntos, como style.color.

TipoUsoKeyframeable
stringCadenas, como un título, prompt o fragmento de códigoNo
numberNúmeros, con min, max, step, integer opcionalesSí
booleanInterruptores de encendido/apagadoSí (mantener)
colorCadenas de color CSSSí
enumUna elección entre variants, cada una con su propio esquema anidadoOpt-in (sostener)
arrayListas de números, colores, enums y másNo
assetFuentes de medios, con assetType opcionalNo
font-familyFamilia de fuente CSSNo
font-weightPeso de fuenteSí
translate, scale, rotation-css, rotation-degrees, transform-originTransformacionesSí
uv-coordinateUn punto [x, y normalizado en el elementoSí
svg-pathDatos de ruta SVGSí
remotion-captionsDatos Caption[]No
hiddenUn valor mantenido fuera de los controles—

Cada campo necesita un default y debe tener una description, que Studio muestra como la etiqueta. Establece keyframable: false para mostrar un control estático.

Expón el contenido y la apariencia por instancia, como el texto y un color de acento. No agregues campos para style.translate, style.scale, style.rotate, style.transformOrigin y style.opacity: Interactive.create() los agrega automáticamente.

Para hacer que las propiedades CSS de la raíz sean editables por instancia, extiende los fragmentos de esquema incorporados:

const cardSchema = {
  ...Interactive.textSchema, // style.color, style.fontSize, style.fontWeight, ...
  ...Interactive.backgroundSchema, // style.backgroundColor
  ...Interactive.borderRadiusSchema, // style.borderRadius, ...
} as const satisfies InteractivitySchema;

También disponibles: Interactive.borderSchema e Interactive.captionsSchema para componentes que aceptan subtítulos. Para componentes que aceptan hijos React.ReactNode, añade ...Interactive.childrenSchema. Proporciona controles para el contenido de los hijos y puede evolucionar para admitir contenido más enriquecido. Actualmente, usa un campo de cadena etiquetado como "Texto" con un valor predeterminado de ''. Si un componente requiere hijos solo de cadena, declara un campo children explícito con type: 'string'. Interactive.textSchema controla los estilos tipográficos; childrenSchema controla el contenido. El fragmento de hijos no está incluido en baseSchema ni se agrega automáticamente a componentes personalizados. El marcado anidado y los hijos computados son actualmente de solo lectura. text-content es un alias obsoleto para el tipo de campo string. Consulta InteractivitySchema para todas las opciones.

Registrar componentes reutilizables como composiciones conectadas

Registra escenas y elementos reutilizables como composiciones independientes. Studio llama a estas composiciones conectadas: se pueden abrir en su propia línea de tiempo mientras comparten la misma implementación de componente con el video principal.

Define los estilos requeridos y carga las fuentes dentro del componente para que funcione sin su padre. Consulta independencia del padre.

Registra la misma referencia de componente exportado que renderiza el padre. Para el ejemplo anterior, usa component={Chapter}, no ChapterInner o un envoltorio en línea. Interactive.create() proporciona la secuencia, por lo que no se necesita un <Sequence> adicional para la conexión.

import {Composition} from 'remotion';
import {Chapter} from './Chapter';

export const RemotionRoot = () => (
  <Composition
    id="Chapter"
    component={Chapter}
    width={1280}
    height={720}
    fps={30}
    durationInFrames={90}
    defaultProps={{
      title: 'Una nueva perspectiva',
      style: {
        backgroundColor: '#111111',
        justifyContent: 'center',
        alignItems: 'center',
      },
    }}
  />
);

Elige un ID único, valores predeterminados en línea representativos y dimensiones, fps y duración adecuados para la vista previa del componente. Los valores predeterminados del registro no copian automáticamente las props de una instancia padre. Consulta composiciones conectadas para detalles de registro y extracción.

Colocar el tiempo directamente en el componente

Evita un <Sequence> separado alrededor de un componente que ya acepta props de tiempo. Los componentes interactivos integrados y los componentes personalizados hechos con Interactive.create() pueden recibir name, from, durationInFrames, trimBefore y playbackRate directamente.

<Chapter
  name="Escena de apertura"
  from={30}
  durationInFrames={90}
  style={{opacity: 0.9}}
  title="Una nueva perspectiva"
/>

Para el tiempo de escena compartido, agrupa el contenido en un componente hecho con Interactive.create() y sincroniza esa instancia. Las raíces administradas también aceptan width y height para useVideoConfig().

Usa <Track> para colocar elementos en la misma fila de la línea de tiempo de Studio. Usa <TransitionSeries> cuando las escenas son secuenciales.

Dar a cada elemento editable independiente su propio nodo JSX

Studio edita el nodo fuente JSX que creó un elemento. Si varios elementos en tiempo de ejecución provienen del mismo nodo JSX, comparten un único objetivo de edición de fuente.

Para cada registro de composición, clip, escena, capa o secuencia que deba ser editable por sí mismo, escribe un nodo JSX separado y mantiene sus props editables en ese nodo. Esto se aplica a <Composition>, <Still>, componentes de medios integrados, <Sequence>, <TransitionSeries.Sequence> y componentes personalizados.

Por ejemplo, autor de una línea de tiempo editable así:

import { Track } from "remotion";

<Track name="Escenas">
  <Introduction name="Introducción" durationInFrames={90} />
  <Demo name="Demostración" from={90} durationInFrames={150} />
</Track>

Un .map() u otro bucle programático crearía múltiples elementos en tiempo de ejecución a partir de un nodo fuente JSX. Eso es apropiado para una salida repetida que se controla intencionalmente como una plantilla, como barras de visualización o partículas. No es apropiado cuando las instancias necesitan IDs o nombres, props, metadatos, sincronización, orden, eliminación o duplicación independientes en Studio.

Hacer un elemento HTML interactivo usando Interactive

Cada elemento HTML y SVG (excepto <Img>, que ya es interactivo) como <div> se puede volver interactivo usando Interactive:

<Interactive.Div
  name="Tarjeta de saludo"
  style={{fontSize: 80, padding: 24}}
>
  Hola
</Interactive.Div>

Esto permite establecer estilos y keyframes en Studio. Sé sensato; si un componente tiene muchos elementos, la línea de tiempo podría volverse desordenada.

Preferir texto en línea

Si el texto es fijo y solo se usa una vez, escríbelo directamente dentro del elemento interactivo en lugar de extraerlo a una constante.

// 👍 El texto fijo permanece editable
<Interactive.Div name="Título">
  Mejores prácticas de Remotion
</Interactive.Div>

Usa una prop o variable solo cuando el texto es dinámico o se reutiliza.

Dar a los elementos interactivos un nombre descriptivo

Añade una prop name a los elementos para hacerlos fácilmente identificables. Evita nombres computados; codifícalos.

<>
  <Interactive.Div name="Título principal" style={{fontSize: 80}}>
    Día de lanzamiento
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Fondo" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Título">
    Día de lanzamiento
  </Interactive.Div>
</>

Mantener todos los estilos CSS en línea

La mejor manera es simplemente pasar un objeto simple a style: sin referencias a constantes, sin propagación de objetos, sin matemáticas.

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  ¡Hola Mundo!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ Los estilos no en línea no son compatibles
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ La propagación no es compatible
    color: RED, // ❌ Referirse a constantes no es compatible
    scale: frame * 10 // ❌ Las matemáticas no son compatibles
  }}
>
  ¡Hola Mundo!
</Interactive.Div>

Animar usando interpolate()

Escribe animaciones como llamadas interpolate() en línea en la propiedad que cambia. El rango de salida, la función de easing, la extrapolación y la propiedad output deben usar valores codificados.

El rango de entrada puede usar adicionalmente durationInFrames, fps, width y height desestructurados directamente de useVideoConfig(). Los identificadores simples como durationInFrames, la multiplicación con un número como 2 * fps o fps * 2, y la resta de un número como durationInFrames - 1 son compatibles.

const {fps, durationInFrames} = useVideoConfig();

// 👍 Los valores en línea pueden ser estandarizados y keyframeables
<Interactive.Div
  name="Tarjeta de producto"
  style={{
    color: 'white',
    fontSize: 80,
    scale: interpolate(frame, [0, fps], [0, 1], {
      easing: Easing.spring({damping: 200}),
      output: 'perceptual-scale',
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], {
      easing: Easing.spring({damping: 200}),
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    translate: interpolate(
      frame,
      [durationInFrames - 30, durationInFrames],
      ['0px 0px', '0px 120px'],
      {
        easing: Easing.spring({damping: 200}),
        output: 'perceptual-scale',
        extrapolateLeft: 'clamp',
        extrapolateRight: 'clamp'
      }
    ),
  }}
/>
const translateY = interpolate(frame, [0, 30], [0, 120]); // ❌ Las matemáticas deben estar directamente en el marcado

<Interactive.Div
  name="Tarjeta de producto"
  style={{
    translate: translateY, // ❌ Solo se admiten llamadas interpolate() en línea,
    rotate: interpolate(frame, [start, start + 10], [0, Math.PI]), // ❌ No se pueden usar matemáticas con variables arbitrarias, no se pueden usar constantes
    scale: interpolate(anyVariable, [0, 30], [0, 1]) // ❌ Solo se puede interpretar la variable `frame`.
  }}
/>

Mantener las rutas SVG editables con Interactive.Path

Usa <Interactive.Path> de remotion en lugar de <path> dentro de un SVG para hacer que la ruta sea visualmente editable.

Instala @remotion/paths para la interpolación de rutas y los keyframes de ruta de Studio:

bunx remotion add @remotion/paths

Si la geometría debe ser estática, coloca la cadena de ruta directamente en la prop d. No la extraigas a una constante.

import {Interactive} from 'remotion';

<Interactive.Svg width={300} height={300} viewBox="0 0 300 300">
  <Interactive.Path
    name="Triángulo"
    d="M 40 40 L 260 40 L 150 260 Z"
    fill="#0b84f3"
  />
</Interactive.Svg>

Rutas de transformación usando interpolación en línea

Individual skills in this repo

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

Skills relacionados