Communitygithub.com

ahmadzulfi/video

Estructura el marcado de Remotion para hacerlo interactivo.

¿Qué es video?

Este skill permite que el código de Remotion sea reconocido y editable visualmente dentro del Remotion Studio. Al estructurar el marcado de una manera específica, los desarrolladores pueden habilitar la selección de elementos mediante clics, así como funcionalidades de arrastrar y soltar, redimensionar y rotar objetos directamente en el Studio. Además, facilita la edición de estilos CSS, keyframes y valores de easing, lo que convierte la composición en una experiencia interactiva. Si el marcado es demasiado complejo, los valores se atenúan para indicar que no son editables. Este skill está diseñado para su uso con el ecosistema de Remotion, y proporciona pautas claras sobre cómo estructurar componentes, utilizar Interactive.withSchema, registrar composiciones conectadas y animar con interpolate(), todo con el objetivo de maximizar la interactividad y la capacidad de edición en el Studio.

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

Documentación

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

  • Permitiendo seleccionar elementos haciendo clic en ellos
  • Permitiendo arrastrar y soltar, redimensionar y rotar
  • Editando los estilos CSS
  • Haciendo editables los keyframes y valores de easing

Si el marcado es demasiado complejo para que el Studio lo haga interactivo, los valores se atenúan.

Preferir componentes interactivos con sus propias líneas de tiempo

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

Mantén los elementos visuales que van 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 generalmente es innecesario. Las tarjetas, cuadros de texto y SVGs con sus propios límites pueden mantener el layout: 'none' predeterminado 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: 'Title'},
} as const satisfies InteractivitySchema;

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

Elegir campos de esquema

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

TipoUsar paraCon keyframes
stringCadenas, como un título, prompt o fragmento de códigoNo
numberNúmeros, con min, max, step, integer opcionalesSí
booleanInterruptores de encendido/apagadoSí (hold)
colorCadenas de color CSSSí
enumUna elección entre variants, cada una con su propio esquema anidadoOpt-in (hold)
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 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 agregues campos para style.translate, style.scale, style.rotate, style.transformOrigin y style.opacity: wrapInSequence: true ya los añade.

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 leyendas. Para componentes que aceptan hijos React.ReactNode, agrega ...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 de solo cadenas, 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 agrega 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 de componente con el video padre.

Define los estilos requeridos y carga las fuentes dentro del componente para que funcione sin su padre. Ver 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 wrapper 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 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 incorporados y los componentes personalizados creados 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. Preserva cualquier tiempo existente del hijo; no sobrescribas ni apliques desplazamientos dobles. Una llamada a useCurrentFrame() dentro del componente lee su fotograma local, mientras que una expresión de estilo en el sitio de llamada aún usa el fotograma de quien llama. Preserva esa distinción al mover estilos de animación.

Mantén un <Sequence> cuando proporciona tiempo compartido para múltiples hermanos, envuelve un componente que no maneja el tiempo, o suministra dimensiones que el hijo no puede aceptar directamente. Las raíces gestionadas 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 de forma independiente su propio nodo JSX

El Studio edita el nodo fuente JSX que creó un elemento. Si múltiples elementos de tiempo de ejecución provienen del mismo nodo JSX, comparten un único destino de edición de fuente.

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

Por ejemplo, crea una línea de tiempo editable de esta manera:

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

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

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

Hacer un elemento HTML interactivo usando Interactive

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

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

Esto permite establecer estilos y keyframes 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 solo se usa una vez, escríbelo directamente dentro del elemento interactivo en lugar de extraerlo en una constante.

// 👍 La copia fija 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

Agrega 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 plano a style: sin referirse a constantes, sin propagación de objetos, sin operaciones 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 operaciones matemáticas no son compatibles
  }}
>
  ¡Hola Mundo!
</Interactive.Div>

Animar usando interpolate()

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

El rango de entrada puede usar además 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 se pueden estandarizar y animar con keyframes
<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 destacado
</Interactive.Div>

Pasar props solo para valores dinámicos

Mantén las props estáticas directamente en el elemento. Pasa props solo para valores que cambian entre instancias o necesitan ser editables. Esto mantiene el marcado limpio y alienta a los editores a usar el Studio para personalizar a nivel de elemento.

// 👍 Estático en el elemento
<Interactive.Div name="Título" style={{fontSize: 80, color: 'white'}}>
  Bienvenido
</Interactive.Div>

// 👍 Dinámico como prop
<Scene title={pageTitle} />

Diseñar para la edición directa

Los elementos que los editores podrían querer reordenar, duplicar o eliminar como una unidad deben tener su propio nodo JSX y name. Los grupos que siempre se mueven juntos (por ejemplo, un icono y su etiqueta) pueden compartir un nodo. Si una parte de un grupo necesita estilos independientes, divídela en su propio nodo en lugar de usar CSS anidado dentro de un solo elemento interactivo.

Prefacio del original

Este documento describe cómo estructurar el marcado de Remotion para que el Remotion Studio pueda reconocer la estructura del código y hacerlo interactivo.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills relacionados