Communitygithub.com

ahmadzulfi/video

Estrutura marcação Remotion para interatividade

O que é video?

Esta skill descreve como estruturar a marcação Remotion para maximizar a interatividade no Remotion Studio. Ela permite que desenvolvedores criem componentes que podem ser selecionados, arrastados, redimensionados e rotacionados diretamente no Studio, além de editar estilos CSS, keyframes e easing values. A habilidade cobre o uso de `Interactive.withSchema()` para componentes preenchíveis, escolha de campos de esquema tipados para propriedades editáveis (como string, number, color, enum, array, asset, font-family, entre outros), e a criação de composições conectadas para reutilização de componentes. Além disso, enfatiza práticas como manter o timing diretamente nos componentes em vez de usar `<Sequence>` redundantes, dar nós JSX separados para itens independentes, usar nomes descritivos e estilos inline, e animar com `interpolate()`. A estrutura suporta múltiplos tipos de campos com opções de keyframing, garantindo que o Studio reconheça e torne editáveis os elementos de forma intuitiva.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ahmadzulfi/video/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 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 torná-la 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 torná-la interativa, os valores ficam acinzentados.

Prefira componentes interativos com suas próprias timelines

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

Mantenha visuais que pertencem juntos no mesmo componente, para que eles se movam e sejam cortados juntos. Um contêiner de layout interno ainda pode fornecer alinhamento, clipping dentro de uma área menor ou um grupo animado; um contêiner que apenas preenche a cena geralmente é desnecessário. Cartões, caixas de texto e SVGs com seus próprios limites podem manter o padrão layout: 'none' e encaminhar 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 ponto 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.

TipoUse paraCom keyframe
stringStrings, como um título, prompt ou trecho de códigoNão
numberNúmeros, com min, max, step, integer opcionaisSim
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 assetType opcionalNão
font-familyFamília de fonte CSSNão
font-weightPeso da fonteSim
translate, scale, rotation-css, rotation-degrees, transform-originTransformaçõesSim
uv-coordinateUm ponto [x, y] normalizado 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 propriedades CSS da raiz editáveis por instância, espalhe os fragmentos de esquema internos:

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 conteúdo filho e pode evoluir para suportar conteúdo mais rico. Atualmente, usa um campo string com rótulo "Text" e padrão ''. 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 de filhos 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 estas de composições conectadas: elas podem ser abertas em sua própria timeline enquanto compartilham a mesma implementação de componente com o vídeo pai.

Defina estilos obrigató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 exportado 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, defaults inline representativos e dimensões, fps e duração adequados para pré-visualizar o componente. Os defaults de registro não copiam automaticamente props de uma instância pai. Veja composições conectadas para registro e detalhes de extração.

Coloque o timing diretamente no componente

Evite um <Sequence> separado em torno de um componente que já aceita props de timing. Componentes interativos internos 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 seu timing e nome para o filho. Preserve qualquer timing filho existente; não sobrescreva ou aplique offsets duplicados. Uma chamada useCurrentFrame() dentro do componente lê seu frame local, enquanto uma expressão de estilo no ponto de chamada ainda usa o frame do chamador. Preserve essa distinção ao mover estilos de animação.

Mantenha um <Sequence> quando ele fornece timing compartilhado para múltiplos irmãos, envolve um componente que não lida com timing ou fornece dimensões que o filho não pode 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ó fonte JSX que criou um item. Se múltiplos itens em tempo de execução vêm do mesmo nó JSX, eles compartilham um único alvo de edição de fonte.

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

Por exemplo, crie uma timeline 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 de um nó fonte JSX. 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 independentes, props, metadados, timing, ordenação, exclusão ou duplicação 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 tiver muitos elementos, a timeline pode ficar confusa.

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ê aos elementos interativos um nome descritivo

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 é simplesmente passar um objeto simples para style - sem referir constantes, sem espalhamento de objeto, 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, // ❌ Referir 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 codificados.

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 com keyframe
<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>

Use useInterpolate() e useReducedMotion()

Remova chamadas diretas a interpolate() em elementos interativos quando possível. Em vez disso, use useInterpolate() e useReducedMotion() de remotion.

useInterpolate() retorna um objeto com propriedades interpoladas que podem ser passadas diretamente para o elemento. useReducedMotion() retorna um valor booleano que pode ser usado para pular animações.

const {fps} = useVideoConfig();
const interpolated = useInterpolate(frame, {
  scale: {
    inputRange: [0, fps],
    outputRange: [0, 1],
    easing: Easing.spring({damping: 200}),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp'
  }
});

<Interactive.Div
  style={{
    ...interpolated.style,
    color: 'white'
  }}
>
  Hello World!
</Interactive.Div>

Use useTiming() para sincronizar timing

Use useTiming() para sincronizar o timing de elementos interativos. Ele retorna um objeto com props de timing que podem ser passadas diretamente para o elemento.

const timing = useTiming({
  from: 30,
  durationInFrames: 90
});

<Interactive.Div
  {...timing}
  name="Animated element"
>
  Conteúdo animado
</Interactive.Div>

Isso simplifica o gerenciamento de timing e garante consistência em toda a timeline.

Use useReducedMotion() para acessibilidade

Use useReducedMotion() para pular animações quando o usuário preferir movimento reduzido. Retorna true quando movimento reduzido é preferido.

const reducedMotion = useReducedMotion();

const animation = reducedMotion
  ? {}
  : {
      scale: interpolate(frame, [0, fps], [0, 1])
    };

<Interactive.Div
  style={{
    ...animation,
    color: 'white'
  }}
>
  Conteúdo acessível
</Interactive.Div>

Isso garante que animações sejam puladas quando o usuário prefere movimento reduzido, melhorando a acessibilidade.

Prefira valores de animação calculados em vez de chaves CSS compostas

Evite chaves CSS compostas como translate, scale, rotate em animações. Em vez disso, use transform com funções de transformação explícitas.

<Interactive.Div
  style={{
    transform: `translateX(${interpolate(frame, [0, fps], [0, 100])}px)`
  }}
>
  Conteúdo animado
</Interactive.Div>
<Interactive.Div
  style={{
    translate: interpolate(frame, [0, fps], [0, 100])
  }}
>
  Conteúdo animado
</Interactive.Div>

Isso garante que animações sejam renderizadas corretamente e evita problemas de compatibilidade com navegadores mais antigos.

Mantenha animações em elementos interativos

Sempre mantenha animações diretamente nos elementos interativos que as utilizam. Isso garante que o Studio possa detectar e editar keyframes corretamente.

<Interactive.Div
  style={{
    fontSize: interpolate(frame, [0, fps], [12, 80])
  }}
>
  Texto animado
</Interactive.Div>
const animatedStyle = {
  fontSize: interpolate(frame, [0, fps], [12, 80])
};

<Interactive.Div
  style={animatedStyle}
>
  Texto animado
</Interactive.Div>

Isso garante que o Studio possa detectar e editar animações, tornando a experiência interativa mais suave.

Use useKeyframe() para animações complexas

Use useKeyframe() para animações complexas que exigem múltiplos keyframes. Ele retorna um objeto com props de animação que podem ser passadas diretamente para o elemento.

const {fps} = useVideoConfig();
const animation = useKeyframe({
  frame,
  fps,
  keyframes: [
    {frame: 0, value: 0},
    {frame: fps, value: 100}
  ],
  easing: Easing.linear
});

<Interactive.Div
  style={{
    transform: `translateX(${animation.value}px)`
  }}
>
  Animação complexa
</Interactive.Div>

Isso simplifica a criação de animações complexas e garante consistência em toda a timeline.

Prefira useCurrentFrame() sobre useVideoConfig().currentFrame

Sempre use useCurrentFrame() para obter o frame atual em vez de useVideoConfig().currentFrame. useCurrentFrame() é mais eficiente e confiável.

const frame = useCurrentFrame();
const {currentFrame} = useVideoConfig();

Isso garante que animações sejam renderizadas corretamente e evita problemas de desempenho.

Mantenha animações síncronas com a timeline

Sempre sincronize animações com a timeline usando useCurrentFrame() ou useTiming(). Evite animações assíncronas que podem dessincronizar com o resto do vídeo.

const frame = useCurrentFrame();
<Interactive.Div
  style={{
    transform: `translateX(${interpolate(frame, [0, fps], [0, 100])}px)`
  }}
>
  Animação síncrona
</Interactive.Div>
<Interactive.Div
  style={{
    transform: `translateX(${Date.now() % 1000}px)`
  }}
>
  Animação assíncrona
</Interactive.Div>

Isso garante que animações sejam consistentes e previsíveis em toda a timeline.

Use useReducedMotion() para animações condicionais

Use useReducedMotion() para pular animações condicionalmente quando o usuário prefere movimento reduzido. Isso melhora a acessibilidade e garante que animações não sejam forçadas em usuários que preferem menos movimento.

const reducedMotion = useReducedMotion();

const animation = reducedMotion
  ? {}
  : {
      transform: `translateX(${interpolate(frame, [0, fps], [0, 100])}px)`
    };

<Interactive.Div
  style={{
    ...animation,
    color: 'white'
  }}
>
  Animação condicional
</Interactive.Div>

Isso garante que animações sejam seguras para todos os usuários e melhora a experiência geral.

Prefira animações CSS a animações JavaScript

Sempre que possível, use animações CSS em vez de animações JavaScript. Animações CSS são mais eficientes e fáceis de editar no Studio.

<Interactive.Div
  style={{
    transition: 'transform 0.5s ease'
  }}
>
  Animação CSS
</Interactive.Div>
<Interactive.Div
  style={{
    transform: `translateX(${interpolate(frame, [0, fps], [0, 100])}px)`
  }}
>
  Animação JavaScript
</Interactive.Div>

Isso garante que animações sejam eficientes e fáceis de editar no Studio.

Use useKeyframe() para animações complexas

Use useKeyframe() para animações complexas que exigem múltiplos keyframes. Ele retorna um objeto com props de animação que podem ser passadas diretamente para o elemento.

const {fps} = useVideoConfig();
const animation = useKeyframe({
  frame,
  fps,
  keyframes: [
    {frame: 0, value: 0},
    {frame: fps, value: 100}
  ],
  easing: Easing.linear
});

<Interactive.Div
  style={{
    transform: `translateX(${animation.value}px)`
  }}
>
  Animação complexa
</Interactive.Div>

Isso simplifica a criação de animações complexas e garante consistência em toda a timeline.

Mantenha animações síncronas com a timeline

Sempre sincronize animações com a timeline usando useCurrentFrame() ou useTiming(). Evite animações assíncronas que podem dessincronizar com o resto do vídeo.

const frame = useCurrentFrame();
<Interactive.Div
  style={{
    transform: `translateX(${interpolate(frame, [0, fps], [0, 100])}px)`
  }}
>
  Animação síncrona
</Interactive.Div>
<Interactive.Div
  style={{
    transform: `translateX(${Date.now() % 1000}px)`
  }}
>
  Animação assíncrona
</Interactive.Div>

Isso garante que animações sejam consistentes e previsíveis em toda a timeline.

Individual skills in this repo

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

Habilidades Relacionadas