Communitygithub.com

Guilhermeslvg/video-portfolio

Estrutura a marcação Remotion para torná-la interativa

O que é video-portfolio?

Este skill orienta desenvolvedores a estruturar componentes e marcações Remotion de forma que o Remotion Studio reconheça a estrutura do código e a torne interativa, permitindo seleção por clique, arrastar e soltar, redimensionamento, rotação e edição de estilos CSS diretamente na interface visual. Ele suporta todas as plataformas que utilizam o ecossistema Remotion, como Claude Code, Cursor e ambientes de desenvolvimento de vídeo programático. A documentação detalha como usar `Interactive.withSchema()` para criar componentes com suas próprias linhas do tempo, como escolher campos de esquema (string, número, cor, enum, array, etc.) com suas respectivas capacidades de keyframe, e como registrar componentes reutilizáveis como composições conectadas para edição independente. Também aborda práticas para manter estilos CSS inline, animar com `interpolate()` usando valores fixos, dar nomes descritivos aos elementos interativos e garantir que cada item editável tenha seu próprio nó JSX, evitando loops que compartilhem a mesma fonte de edição.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Prévia

Do README da skill

video-portfolio preview

Documentação

Ao escrever a marcação Remotion de uma forma específica, o Remotion Studio consegue reconhecer a estrutura do código e a torna interativa:

  • Permitindo que itens sejam selecionados clicando neles
  • Permitindo arrastar e soltar, redimensionar e rotacionar
  • Editando os estilos CSS
  • Tornando keyframes e valores de easing editáveis

Se a marcação for muito complexa para o Studio tornar interativa, os valores ficam esmaecidos.

Prefira componentes interativos com suas próprias linhas do tempo

Use Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'}) para componentes que preenchem seu pai. O wrapper fornece sua raiz visual e item de linha do tempo, e corta o estouro por padrão, para que o componente interno possa retornar um fragmento. Coloque temporização, premounting, dimensões e estilos por instância no componente exportado.

Mantenha visuais que pertencem juntos no mesmo componente, para que se movam e cortem juntos. Um contêiner de layout interno ainda pode fornecer alinhamento, recorte dentro de uma área menor ou um grupo animado; um contêiner que apenas preenche a cena geralmente é desnecessário. Cards, caixas de texto e SVGs com seus próprios limites podem manter o padrão layout: 'none' e repassar style para sua raiz 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',
});

Escolha campos de esquema

Cada chave no esquema é uma prop que o Studio pode ler e editar no local de chamada. Props que não estão no esquema ainda funcionam, mas não são editáveis no Studio. Chaves podem usar notação de ponto, como style.color.

TipoUso paraCom keyframe
stringStrings, como título, prompt ou trecho de códigoNão
numberNúmeros, com opcional min, max, step, integerSim
booleanInterruptores liga/desligaSim (hold)
colorStrings de cor CSSSim
enumUma escolha entre variants, cada um com seu próprio esquema aninhadoOpt-in (hold)
arrayListas de números, cores, enums e maisNão
assetFontes de mídia, com opcional assetTypeNão
font-familyFamília de fonte CSSNão
font-weightPeso da fonteSim
translate, scale, rotation-css, rotation-degrees, transform-originTransformaçõesSim
uv-coordinateUm ponto normalizado [x, y] no elementoSim
svg-pathDados de caminho SVGSim
remotion-captionsDados Caption[]Não
hiddenUm valor mantido fora dos controles—

Cada campo precisa de um default e deve ter uma description, que o Studio mostra como rótulo. Defina keyframable: false para mostrar um controle estático.

Exponha conteúdo e aparência por instância, como o texto e uma cor de destaque. Não adicione campos para style.translate, style.scale, style.rotate, style.transformOrigin e style.opacity: wrapInSequence: true já os adiciona.

Para tornar as propriedades CSS da raiz editáveis por instância, espalhe os fragmentos de esquema embutidos:

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

Também disponíveis: Interactive.borderSchema, e Interactive.captionsSchema para componentes que aceitam legendas. Para componentes que aceitam filhos React.ReactNode, adicione ...Interactive.childrenSchema. Ele fornece controles para o conteúdo dos filhos e pode evoluir para suportar conteúdo mais rico. Atualmente, usa um campo string rotulado como "Text" com um padrão de ''. Se um componente requer filhos apenas de string, declare um campo children explícito com type: 'string'. Interactive.textSchema controla estilos tipográficos; childrenSchema controla conteúdo. O fragmento children não está incluído em baseSchema ou adicionado automaticamente a componentes personalizados. Marcação aninhada e filhos computados são atualmente somente leitura. text-content é um alias obsoleto para o tipo de campo string. Veja InteractivitySchema para todas as opções.

Registre componentes reutilizáveis como composições conectadas

Registre cenas substanciais e componentes reutilizáveis com suas próprias camadas ou animação como composições independentes. O Studio chama essas composições conectadas: elas podem ser abertas em sua própria linha do tempo enquanto compartilham a mesma implementação de componente com o vídeo pai.

Defina estilos necessários e carregue fontes dentro do componente para que funcione sem seu pai. Veja independência do pai.

Registre a mesma referência de componente exportada que o pai renderiza. Para o exemplo acima, use component={Scene}, não SceneInner ou um wrapper inline. Com wrapInSequence: true, nenhum <Sequence> extra é necessário para a conexão.

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: 'A new perspective',
    }}
  />
);

Escolha um ID único, padrões inline representativos e dimensões, fps e duração adequados para visualizar o componente. Os padrões de registro não copiam automaticamente props de uma instância pai. Veja composições conectadas para detalhes de registro e extração.

Coloque a temporização diretamente no componente

Evite um <Sequence> separado em torno de um componente que já aceita props de temporização. Componentes interativos embutidos e componentes personalizados feitos com wrapInSequence: true podem receber name, from, durationInFrames, trimBefore, playbackRate e premountFor diretamente.

<Scene
  name="Opening scene"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="A new perspective"
/>

Ao remover uma sequência redundante, mova sua temporização e nome para o filho. Preserve qualquer temporização filho existente; não sobrescreva ou aplique deslocamentos duplicados. Uma chamada useCurrentFrame() dentro do componente lê seu quadro local, enquanto uma expressão de estilo no local de chamada ainda usa o quadro do chamador. Preserve essa distinção ao mover estilos de animação.

Mantenha um <Sequence> quando ele fornecer temporização compartilhada para múltiplos irmãos, envolver um componente que não lida com temporização ou fornecer dimensões que o filho não possa aceitar diretamente. Raízes gerenciadas também aceitam width e height para useVideoConfig(). Mantenha <TransitionSeries.Sequence> para layout consecutivo, com ou sem transições; props from diretas não substituem esses comportamentos.

Dê a cada item editável independente seu próprio nó JSX

O Studio edita o nó JSX fonte que criou um item. Se vários itens em tempo de execução vêm do mesmo nó JSX, eles compartilham um alvo de edição de fonte.

Para cada registro de composição, clipe, cena, camada ou sequência que deve ser editável por conta própria, escreva um nó JSX separado e mantenha suas props editáveis nesse nó. Isso se aplica a <Composition>, <Still>, componentes de mídia embutidos, <Sequence>, <TransitionSeries.Sequence> e componentes personalizados.

Por exemplo, crie uma linha do tempo editável assim:

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

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

Um .map() ou outro loop programático criaria múltiplos itens em tempo de execução a partir de um nó JSX fonte. Isso é apropriado para saída repetida que é intencionalmente controlada como um modelo, como barras de visualização ou partículas. Não é apropriado quando as instâncias precisam de IDs ou nomes, props, metadados, temporização, ordenação, exclusão ou duplicação independentes no Studio.

Torne um elemento HTML interativo usando Interactive

Todo elemento HTML e SVG (exceto <Img>, que já é interativo), como <div>, pode se tornar interativo usando Interactive:

<Interactive.Div
  name="Greeting card"
  style={{fontSize: 80, padding: 24}}
>
  Hello
</Interactive.Div>

Isso permite que estilos e keyframes sejam definidos no Studio. Seja sensato: se um componente tem muitos elementos, a linha do tempo pode ficar bagunçada.

Prefira texto inline

Se o texto é fixo e usado apenas uma vez, escreva-o diretamente dentro do elemento interativo em vez de extraí-lo para uma constante.

// 👍 Cópia fixa permanece editável
<Interactive.Div name="Title">
  Remotion Best Practices
</Interactive.Div>

Use uma prop ou variável apenas quando o texto é dinâmico ou reutilizado.

Dê nomes descritivos aos elementos interativos

Adicione uma prop name aos elementos para torná-los facilmente identificáveis. Evite nomes computados; codifique-os.

<>
  <Interactive.Div name="Hero title" style={{fontSize: 80}}>
    Launch day
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Background" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Title">
    Launch day
  </Interactive.Div>
</>

Mantenha todos os estilos CSS inline

A melhor maneira é apenas passar um objeto simples para style - sem referenciar constantes, sem espalhar objetos, sem matemática.

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  Hello World!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ Estilos não inline não são suportados
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ Espalhamento não é suportado
    color: RED, // ❌ Referenciar constantes não é suportado
    scale: frame * 10 // ❌ Matemática não é suportada
  }}
>
  Hello World!
</Interactive.Div>

Anime usando interpolate()

Escreva animações como chamadas interpolate() inline na propriedade que muda. O intervalo de saída, easing, extrapolação e a propriedade output devem usar valores fixos.

O intervalo de entrada pode adicionalmente usar durationInFrames, fps, width e height desestruturados diretamente de useVideoConfig(). Identificadores simples como durationInFrames, multiplicação com um número como 2 * fps ou fps * 2, e subtração de um número como durationInFrames - 1 são suportados.

const {fps, durationInFrames} = useVideoConfig();

// 👍 Valores inline podem ser padronizados e ter keyframes
<Interactive.Div
  name="Product card"
  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'
      }
    )
  }}
>
  Hello World!
</Interactive.Div>

Expressões de retorno de chamada não são armazenáveis. Em vez disso, escolha easing, extrapolateLeft e extrapolateRight explícitos. Apenas easing de Easing e valores predefinidos são suportados.

Individual skills in this repo

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

Habilidades Relacionadas