Communitygithub.com

Guilhermeslvg/video-portfolio

Estructura el marcado de Remotion para lograr interactividad

¿Qué es video-portfolio?

Esta habilidad define un conjunto de prácticas y patrones para estructurar el código de componentes Remotion de manera que el Remotion Studio pueda reconocer su estructura y hacerlos interactivos. Permite que los elementos sean seleccionables mediante clics, arrastrables, redimensionables y rotables, y que sus estilos CSS, fotogramas clave (keyframes) y valores de easing sean editables visualmente. Utiliza el componente `Interactive.withSchema()` para envolver componentes, proporcionándoles su propia línea de tiempo, y recomienda el uso de esquemas tipados (`InteractivitySchema`) para definir props editables como strings, números, colores o valores de transformación. También promueve la creación de composiciones conectadas, el uso de nodos JSX separados para elementos editables de forma independiente, la escritura de estilos CSS en línea, y la animación mediante `interpolate()`, todo con el objetivo de maximizar la interactividad y la capacidad de edición visual en el Studio.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Guilhermeslvg/video-portfolio/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-portfolio preview

Documentación

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

  • Permitiendo seleccionar elementos haciendo clic sobre ellos
  • Permitiendo arrastrar y soltar, redimensionar y rotar
  • Editando los estilos CSS
  • Haciendo que los fotogramas clave y los valores de easing sean editables

Si el marcado es demasiado complejo para que el Studio lo haga interactivo, los valores se mostrarán en gris.

Preferir componentes interactivos con sus propias líneas de tiempo

Usa Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) para componentes que llenan su contenedor padre. El envoltorio proporciona su raíz visual y un elemento de línea de tiempo, y recorta el desbordamiento por defecto, por lo que el componente interno puede devolver un fragmento. Coloca el tiempo, el precargado, las dimensiones y los estilos por instancia en el componente exportado.

Mantén los elementos visuales que pertenecen juntos en el mismo componente, para que se muevan y recorten juntos. Un contenedor de diseño interno aún puede proporcionar alineación, recorte dentro de un área más pequeña o un grupo animado; un contenedor que solo llena la escena normalmente es innecesario. Las tarjetas, los cuadros de texto y los SVG con sus propios límites pueden mantener el valor por defecto layout: 'none' y pasar style a su raíz visual.

import {Video} from '@remotion/media';
import type React from 'react';
import {Interactive, useVideoConfig, type InteractivitySchema} from 'remotion';

type SceneProps = {
  readonly title: string;
};

const SceneInner: React.FC<SceneProps> = ({title}) => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video
        name="Background footage"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="Scene title"
        style={{
          position: 'absolute',
          left: 80,
          bottom: 80,
          color: 'white',
          fontFamily: 'Helvetica, Arial, sans-serif',
          fontSize: 80,
        }}
      >
        {title}
      </Interactive.Div>
    </>
  );
};

const sceneSchema = {
  title: {type: 'string', default: '', description: 'Título'},
} as const satisfies InteractivitySchema;

export const Scene = Interactive.withSchema({
  Component: SceneInner,
  componentName: 'Scene',
  schema: sceneSchema,
  wrapInSequence: true,
  layout: 'absolute-fill',
});

Elegir campos del esquema

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

TipoÚsalo paraFotogramas clave
stringCadenas, como un título, prompt o fragmento de códigoNo
numberNúmeros, con opcionales min, max, step, integerSí
booleanInterruptores de encendido/apagadoSí (mantener)
colorCadenas de color CSSSí
enumUna elección entre variants, cada una con su propio esquema anidadoOpt-in (mantener)
arrayListas de números, colores, enums y másNo
assetFuentes de medios, con opcional assetTypeNo
font-familyFamilia tipográfica CSSNo
font-weightPeso de la fuenteSí
translate, scale, rotation-css, rotation-degrees, transform-originTransformacionesSí
uv-coordinateUn punto normalizado [x, y] 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 un description, que el 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 añadas campos para style.translate, style.scale, style.rotate, style.transformOrigin y style.opacity: wrapInSequence: true ya los añade.

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

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 por defecto de ''. Si un componente requiere hijos de solo cadena, declara un campo children explícito con type: 'string' en su lugar. Interactive.textSchema controla los estilos tipográficos; childrenSchema controla el contenido. El fragmento de hijos no está incluido en baseSchema ni se añade automáticamente a componentes personalizados. El marcado anidado y los hijos calculados 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 sustanciales y componentes reutilizables con sus propias capas o animación como composiciones independientes. El Studio llama a estas composiciones conectadas: se pueden abrir en su propia línea de tiempo mientras comparten la misma implementación del componente con el video padre.

Define los estilos necesarios 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 el padre renderiza. Para el ejemplo anterior, usa component={Scene}, no SceneInner o un envoltorio en línea. Con wrapInSequence: true, no se necesita un <Sequence> adicional para la conexión.

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

export const RemotionRoot = () => (
  <Composition
    id="Scene"
    component={Scene}
    width={1280}
    height={720}
    fps={30}
    durationInFrames={30}
    defaultProps={{
      title: 'Una nueva perspectiva',
    }}
  />
);

Elige un ID único, valores predeterminados en línea representativos y dimensiones, fps y duración adecuados para previsualizar el componente. Los valores predeterminados de registro no copian automáticamente las props de una instancia padre. Consulta composiciones conectadas para obtener 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 wrapInSequence: true pueden recibir name, from, durationInFrames, trimBefore, playbackRate y premountFor directamente.

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

Al eliminar una secuencia redundante, mueve su tiempo y nombre al hijo. Conserva cualquier tiempo hijo existente; no sobrescribas ni apliques dos veces los desplazamientos. Una llamada a useCurrentFrame() dentro del componente lee su fotograma local, mientras que una expresión de estilo en el lugar de uso aún usa el fotograma del llamante. Conserva esa distinción al mover estilos de animación.

Mantén un <Sequence> cuando proporcione tiempo compartido para múltiples hermanos, envuelva un componente que no maneja el tiempo, o suministre dimensiones que el hijo no puede aceptar directamente. Las raíces administradas también aceptan width y height para useVideoConfig(). Mantén <TransitionSeries.Sequence> para diseño consecutivo, con o sin transiciones; las props from directas no reemplazan esos comportamientos.

Dar a cada elemento editable independiente su propio nodo JSX

El Studio edita el nodo fuente JSX que creó un elemento. Si múltiples 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 mantén sus props editables en ese nodo. Esto se aplica a <Composition>, <Still>, componentes de medios integrados, <Sequence>, <TransitionSeries.Sequence> y componentes personalizados.

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

import { TransitionSeries } from "@remotion/transitions";

<TransitionSeries>
  <TransitionSeries.Sequence name="Introducción" durationInFrames={90}>
    <Introducción />
  </TransitionSeries.Sequence>
  <TransitionSeries.Sequence name="Demostración" durationInFrames={150}>
    <Demostración />
  </TransitionSeries.Sequence>
</TransitionSeries>

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

Hacer un elemento HTML interactivo usando Interactive

Todos los elementos HTML y SVG (excepto <Img>, que ya es interactivo) como <div> se pueden convertir en interactivos usando Interactive:

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

Esto permite establecer estilos y fotogramas clave en el 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 se usa solo una vez, escríbelo directamente dentro del elemento interactivo en lugar de extraerlo en 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 sea dinámico o se reutilice.

Dar a los elementos interactivos un nombre descriptivo

Añade una prop name a los elementos para hacerlos fácilmente identificables. Evita nombres calculados; 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 esparcir 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, // ❌ El esparcido 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 sobre la propiedad que cambia. El rango de salida, el 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(). Identificadores simples como durationInFrames, multiplicación con un número como 2 * fps o fps * 2, y resta de un número como durationInFrames - 1 son compatibles.

const {fps, durationInFrames} = useVideoConfig();

// 👍 Los valores en línea pueden ser estandarizados y tener fotogramas clave
<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}),
        extrapolateLeft: 'clamp',
        extrapolateRight: 'clamp'
      }
    )
  }}
>
  Producto increíble
</Interactive.Div>

Usar fotogramas clave con estilos en línea

Usa la extensión de sintaxis keyframes={...} para definir animaciones que pueden ser editadas visualmente en el Studio. Esto funciona mejor cuando se combina con Interactive.Div y otros elementos interactivos. La animación se puede controlar desde el panel de fotogramas clave del Studio.

<Interactive.Div
  name="Elemento animado"
  style={{
    fontSize: 80,
  }}
  keyframes={{
    fontSize: {
      0: { value: 80 },
      30: { value: 120 },
      60: { value: 80, easing: Easing.in(Easing.ease) },
    },
    color: {
      0: { value: 'red' },
      50: { value: 'blue' },
      100: { value: 'red' },
    },
  }}
>
  Animación con fotogramas clave
</Interactive.Div>

Manejar propiedades dinámicas con Dynamic.Div

Para propiedades CSS que cambian dinámicamente y que aún necesitan ser editables, usa Dynamic.Div. Esto es útil cuando la animación depende del estado o de cálculos externos, pero aún quieres que los fotogramas clave sean visibles y editables.

<Dynamic.Div
  name="Elemento dinámico"
  style={{
    width: 200,
    height: 200,
    backgroundColor: 'blue',
    borderRadius: dynamicValue, // pasa el valor dinámico aquí
  }}
>
  Contenido dinámico
</Dynamic.Div>

Usar useDynamic() para extraer valores dinámicos

Cuando necesitas usar un valor dinámico dentro de JSX, usa el hook useDynamic() para extraerlo de una prop o estado y hacerlo editable en el Studio.

const MyComponent = ({title}) => {
  const dynamicTitle = useDynamic(title, 'string', {default: 'Título por defecto'});
  
  return (
    <Interactive.Div name="Título" style={{fontSize: 40}}>
      {dynamicTitle}
    </Interactive.Div>
  );
};

Configurar audio interactivo

Los componentes de audio se pueden hacer interactivos de manera similar a los de video, permitiendo que el Studio controle su volumen, inicio y duración.

<Audio
  name="Música de fondo"
  src="https://remotion.media/audio.mp3"
  volume={0.5}
  premountFor={fps}
/>

También puedes usar Interactive.withSchema() para componentes que contienen audio y exponer el volumen como un campo del esquema.

Mejores prácticas de rendimiento

  • Minimiza el número de elementos interactivos: Cada elemento interactivo añade una cierta sobrecarga. Úsalos con moderación.
  • Anida estratégicamente: Agrupa elementos visuales relacionados en el mismo componente interactivo para reducir la complejidad de la línea de tiempo.
  • Prefiere componentes puros: Los componentes interactivos deben ser funciones puras de sus props para una animación predecible.
  • Usa React.memo con cuidado: El memoizado puede interferir con la detección de cambios del Studio; pruébalo a fondo.

Conclusión

Siguiendo estas pautas, puedes crear componentes Remotion que sean altamente interactivos y editables en el Remotion Studio. Esto mejora el flujo de trabajo, permitiendo a los diseñadores y editores ajustar elementos visuales sin tocar el código fuente.

Individual skills in this repo

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

Skills relacionados