Communitygithub.com

ahmadzulfi/video

대화형 Remotion UI 작성을 위한 Remotion 마크업 구조화

video란 무엇인가요?

이 스킬은 Remotion Studio 내에서 비디오 편집 인터페이스를 대화형으로 만들기 위해 Remotion 마크업을 구조화하는 방법을 설명합니다. 개발자가 Interactive 구성 요소와 스키마를 사용하여 요소 선택, 드래그 앤 드롭, 크기 조정, 회전, CSS 스타일 편집, 키프레임 및 이징 값 편집을 가능하게 합니다. 이 스킬은 모든 HTML 및 SVG 요소를 Interactive로 변환하고, 설명적인 이름을 사용하며, CSS 스타일을 인라인으로 유지하고, `interpolate()`를 사용하여 애니메이션을 적용하는 모범 사례를 다룹니다. 또한 `Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})`을 사용하여 구성 요소를 자체 타임라인과 함께 등록하고, `Composition`을 통해 연결된 컴포지션을 생성하는 방법을 보여줍니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 에이전트 플랫폼에서 사용할 수 있으며, Remotion Studio의 대화형 기능을 최대한 활용하려는 개발자에게 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ahmadzulfi/video/tree/HEAD/.agents/skills/remotion-interactivity

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

미리보기

스킬 README에서

video preview

문서

Remotion 마크업을 특정 방식으로 작성하면 Remotion Studio가 코드 구조를 인식하고 대화형으로 만듭니다:

  • 항목을 클릭하여 선택 가능
  • 드래그 앤 드롭, 크기 조정 및 회전 가능
  • CSS 스타일 편집 가능
  • 키프레임 및 이징 값 편집 가능

마크업이 Studio가 대화형으로 만들기에 너무 복잡하면 값이 회색으로 표시됩니다.

자체 타임라인이 있는 대화형 구성 요소 선호

부모를 채우는 구성 요소에는 Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})를 사용하세요. 래퍼는 시각적 루트와 타임라인 항목을 제공하고 기본적으로 오버플로를 클리핑하므로 내부 구성 요소는 프래그먼트를 반환할 수 있습니다. 타이밍, 프리마운팅, 치수 및 인스턴스별 스타일은 내보낸 구성 요소에 배치하세요.

함께 속하는 시각적 요소는 동일한 구성 요소에 유지하여 함께 이동 및 트리밍되도록 하세요. 내부 레이아웃 컨테이너는 여전히 정렬, 더 작은 영역 내 클리핑 또는 애니메이션 그룹을 제공할 수 있습니다. 장면만 채우는 컨테이너는 일반적으로 불필요합니다. 자체 경계가 있는 카드, 텍스트 상자 및 SVG는 기본 layout: 'none'을 유지하고 style을 시각적 루트에 전달할 수 있습니다.

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="배경 영상"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="장면 제목"
        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: '제목'},
} as const satisfies InteractivitySchema;

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

스키마 필드 선택

스키마의 각 키는 Studio가 호출 지점에서 읽고 편집할 수 있는 prop입니다. 스키마에 없는 prop도 계속 작동하지만 Studio에서 편집할 수 없습니다. 키는 style.color와 같은 점 표기법을 사용할 수 있습니다.

유형사용처키프레임 가능
string제목, 프롬프트 또는 코드 조각과 같은 문자열아니요
number선택적 min, max, step, integer가 있는 숫자예
boolean켜기/끄기 스위치예 (유지)
colorCSS 색상 문자열예
enum각각 자체 중첩 스키마가 있는 variants 중에서 선택선택 (유지)
array숫자, 색상, 열거형 등의 목록아니요
asset선택적 assetType이 있는 미디어 소스아니요
font-familyCSS 글꼴 패밀리아니요
font-weight글꼴 두께예
translate, scale, rotation-css, rotation-degrees, transform-origin변환예
uv-coordinate요소의 정규화된 [x, y] 포인트예
svg-pathSVG 경로 데이터예
remotion-captionsCaption[] 데이터아니요
hidden컨트롤 밖에 유지되는 값—

모든 필드에는 default가 필요하며 Studio에서 레이블로 표시되는 description이 있어야 합니다. keyframable: false로 설정하여 정적 컨트롤을 표시합니다.

콘텐츠 및 인스턴스별 모양(예: 텍스트 및 강조 색상)을 노출합니다. style.translate, style.scale, style.rotate, style.transformOrigin 및 style.opacity에 대한 필드를 추가하지 마세요. wrapInSequence: true가 이미 추가합니다.

루트의 CSS 속성을 인스턴스별로 편집 가능하게 만들려면 내장 스키마 프래그먼트를 펼치세요:

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

또한 사용 가능: Interactive.borderSchema 및 캡션을 허용하는 구성 요소용 Interactive.captionsSchema. React.ReactNode 자식을 허용하는 구성 요소의 경우 ...Interactive.childrenSchema를 추가하세요. 자식 콘텐츠에 대한 컨트롤을 제공하며 더 풍부한 콘텐츠를 지원하도록 발전할 수 있습니다. 현재 기본값이 ''인 "텍스트" 레이블의 문자열 필드를 사용합니다. 구성 요소에 문자열 전용 자식이 필요한 경우 대신 type: 'string'으로 명시적 children 필드를 선언하세요. Interactive.textSchema는 타이포그래피 스타일을 제어합니다. childrenSchema는 콘텐츠를 제어합니다. 자식 프래그먼트는 baseSchema에 포함되지 않으며 사용자 정의 구성 요소에 자동으로 추가되지 않습니다. 중첩 마크업 및 계산된 자식은 현재 읽기 전용입니다. text-content는 string 필드 유형의 더 이상 사용되지 않는 별칭입니다. 모든 옵션은 InteractivitySchema를 참조하세요.

재사용 가능한 구성 요소를 연결된 컴포지션으로 등록

자체 레이어 또는 애니메이션이 있는 중요한 장면 및 재사용 가능한 구성 요소를 독립 실행형 컴포지션으로 등록하세요. Studio는 이를 연결된 컴포지션이라고 부릅니다. 부모 비디오와 동일한 구성 요소 구현을 공유하면서 자체 타임라인에서 열 수 있습니다.

구성 요소 내에 필수 스타일을 정의하고 글꼴을 로드하여 부모 없이도 작동하도록 하세요. 부모 독립성을 참조하세요.

부모가 렌더링하는 동일한 내보낸 구성 요소 참조를 등록하세요. 위의 예에서는 SceneInner 또는 인라인 래퍼가 아닌 component={Scene}을 사용하세요. wrapInSequence: true를 사용하면 연결에 추가 <Sequence>가 필요하지 않습니다.

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: '새로운 관점',
    }}
  />
);

고유 ID, 대표적인 인라인 기본값 및 구성 요소 미리보기에 적합한 치수, fps 및 지속 시간을 선택하세요. 등록 기본값은 부모 인스턴스에서 자동으로 prop을 복사하지 않습니다. 등록 및 추출 세부 정보는 연결된 컴포지션을 참조하세요.

타이밍을 구성 요소에 직접 배치

이미 타이밍 prop을 허용하는 하나의 구성 요소 주위에 별도의 <Sequence>를 피하세요. wrapInSequence: true로 만든 기본 제공 대화형 구성 요소 및 사용자 정의 구성 요소는 name, from, durationInFrames, trimBefore, playbackRate 및 premountFor를 직접 받을 수 있습니다.

<Scene
  name="오프닝 장면"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="새로운 관점"
/>

중복 시퀀스를 제거할 때 해당 타이밍과 이름을 자식으로 이동하세요. 기존 자식 타이밍을 유지하고 오프셋을 덮어쓰거나 이중 적용하지 마세요. 구성 요소 내부의 useCurrentFrame() 호출은 로컬 프레임을 읽는 반면 호출 지점의 스타일 표현식은 여전히 호출자의 프레임을 사용합니다. 애니메이션 스타일을 이동할 때 이 구분을 유지하세요.

<Sequence>는 여러 형제에 대한 공유 타이밍을 제공하거나, 타이밍을 처리하지 않는 구성 요소를 래핑하거나, 자식이 직접 수락할 수 없는 치수를 제공할 때 유지하세요. 관리되는 루트는 useVideoConfig()에 대해 width 및 height도 허용합니다. 연속 레이아웃을 위해 <TransitionSeries.Sequence>를 유지하세요(전환 유무와 관계없이). 직접 from prop은 이러한 동작을 대체하지 않습니다.

독립적으로 편집 가능한 모든 항목에 자체 JSX 노드 제공

Studio는 항목을 만든 JSX 소스 노드를 편집합니다. 여러 런타임 항목이 동일한 JSX 노드에서 오면 하나의 소스 편집 대상을 공유합니다.

자체적으로 편집 가능해야 하는 모든 컴포지션 등록, 클립, 장면, 레이어 또는 시퀀스에 대해 별도의 JSX 노드를 작성하고 편집 가능한 prop을 해당 노드에 유지하세요. 이는 <Composition>, <Still>, 기본 제공 미디어 구성 요소, <Sequence>, <TransitionSeries.Sequence> 및 사용자 정의 구성 요소에 적용됩니다.

예를 들어, 다음과 같이 편집 가능한 타임라인을 작성하세요:

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

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

.map() 또는 다른 프로그래밍 방식 루프는 하나의 JSX 소스 노드에서 여러 런타임 항목을 생성합니다. 이는 시각화 막대 또는 입자와 같이 의도적으로 하나의 템플릿으로 제어되는 반복 출력에 적합합니다. 인스턴스에 Studio에서 독립적인 ID 또는 이름, prop, 메타데이터, 타이밍, 순서, 삭제 또는 복제가 필요한 경우 적합하지 않습니다.

Interactive를 사용하여 HTML 요소를 대화형으로 만들기

모든 HTML 및 SVG 요소(<Img> 제외, 이미 대화형임)는 Interactive를 사용하여 대화형으로 만들 수 있습니다:

<Interactive.Div
  name="인사말 카드"
  style={{fontSize: 80, padding: 24}}
>
  안녕하세요
</Interactive.Div>

이렇게 하면 Studio에서 스타일과 키프레임을 설정할 수 있습니다. 현명하게 사용하세요. 구성 요소에 요소가 많으면 타임라인이 지저분해질 수 있습니다.

인라인 텍스트 선호

텍스트가 고정되어 있고 한 번만 사용되는 경우 상수로 추출하는 대신 대화형 요소 내부에 직접 작성하세요.

// 👍 고정된 복사본은 편집 가능하게 유지됨
<Interactive.Div name="제목">
  Remotion 모범 사례
</Interactive.Div>

텍스트가 동적이거나 재사용되는 경우에만 prop 또는 변수를 사용하세요.

대화형 요소에 설명적인 이름 지정

요소에 name prop을 추가하여 쉽게 식별할 수 있도록 하세요. 계산된 이름은 피하고 하드코딩하세요.

<>
  <Interactive.Div name="히어로 제목" style={{fontSize: 80}}>
    출시일
  </Interactive.Div>
  <Img name="아바타" src="https://remotion.media/image.jpeg" />
  <Video name="배경" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="제목">
    출시일
  </Interactive.Div>
</>

모든 CSS 스타일을 인라인으로 유지

가장 좋은 방법은 style에 일반 객체를 전달하는 것입니다. 상수 참조, 객체 확산 또는 수학 연산은 피하세요.

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  Hello World!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ 인라인이 아닌 스타일은 지원되지 않음
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ 확산은 지원되지 않음
    color: RED, // ❌ 상수 참조는 지원되지 않음
    scale: frame * 10 // ❌ 수학 연산은 지원되지 않음
  }}
>
  Hello World!
</Interactive.Div>

interpolate()를 사용하여 애니메이션 적용

변경되는 속성에 인라인 interpolate() 호출로 애니메이션을 작성하세요. 출력 범위, 이징, 외삽 및 output 속성은 하드코딩된 값을 사용해야 합니다.

입력 범위는 추가로 useVideoConfig()에서 직접 구조 분해된 durationInFrames, fps, width 및 height를 사용할 수 있습니다. durationInFrames와 같은 단순 식별자, 2 * fps 또는 fps * 2와 같은 숫자 곱셈, durationInFrames - 1과 같은 숫자 빼기가 지원됩니다.

const {fps, durationInFrames} = useVideoConfig();

// 👍 인라인 값은 표준화 및 키프레임화 가능
<Interactive.Div
  name="제품 카드"
  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'
      }
    ),
  }}
>
  한정 판매!
</Interactive.Div>

일반적인 실수 방지

  • wrapInSequence: true가 있는 구성 요소 내부에 <Sequence>를 중첩하지 마세요. 중첩된 시퀀스의 타이밍을 구성 요소의 from 및 durationInFrames prop으로 옮기고 중복 시퀀스를 제거하세요.
  • 스키마에서 중복 prop을 정의하지 마세요 (style.translate, style.scale, style.rotate, style.transformOrigin, style.opacity는 이미 wrapInSequence: true에 의해 추가됨).
  • 부모가 전달하는 스타일을 덮어쓰지 마세요. 구성 요소가 style prop을 수락하는 경우, 특히 wrapInSequence: true로 래핑된 경우 부모가 제공하는 스타일을 최종 요소에 적용하세요.

참조

Individual skills in this repo

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

관련 스킬