Communitygithub.com

Guilhermeslvg/video-portfolio

콘텐츠, 애니메이션 및 효과 모범 사례

video-portfolio란 무엇인가요?

이 스킬은 Remotion 기반 비디오 포트폴리오 제작을 위한 포괄적인 지침을 제공합니다. React 마크업을 사용하여 동영상을 생성하는 Remotion의 모범 사례와 Studio 환경에 최적화된 편집 가능한 컴포넌트 설계 방법을 자세히 설명합니다. 특히 `useCurrentFrame()`, `interpolate()`, `Easing.bezier()`, `Easing.spring()`을 활용한 인라인 키프레임 애니메이션 기법을 강조하며, CSS transition이나 animation 사용을 금지합니다. 또한 비디오, 오디오, 이미지, GIF 등 다양한 미디어 에셋을 `staticFile()`을 통해 public 폴더에서 참조하거나 원격 URL을 직접 사용하는 방법을 안내하며, 모든 미디어 및 대화형 컴포넌트에 `premountFor={fps}`를 적용하여 부드러운 재생을 보장합니다. Studio에서 독립적으로 편집 가능한 레이어 구조를 위해 `Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})`를 사용한 대화형 컴포넌트 설계를 권장하며, 하위 타이밍을 가진 씬을 별도의 연결된 컴포지션(Connected Composition)으로 등록하는 워크플로를 지원합니다. 추가로 텍스트 하이라이트, 전환 효과, 모션 블러, 크로핑, 음성 해설(ElevenLabs TTS 연동), 비디오/오디오 임베딩 및 고급 효과(`@remotion/effects`)에 대한 상세한 참조 문서 링크도 포함되어 있습니다.

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

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

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

미리보기

스킬 README에서

문서

이는 Remotion React 마크업 작성을 위한 지침입니다. 관련이 없는 경우 대신 Remotion 모범 사례를 불러오세요.

사용자 변경 사항 보존

사용자는 대화 외부에서 코드를 편집할 수 있습니다.

그 사이에 놀라운 변경 사항이 감지되면 덮어쓰지 말고, 의도적인 것으로 간주하거나 확인을 요청하세요.

일반 규칙

컴포지션이나 씬 컴포넌트 내부에서는 useVideoConfig()에서 fps를 가져와 모든 초-프레임 계산에 사용하세요. 여기에는 from, trimBefore, durationInFrames, premountFor와 같은 타이밍 속성이 포함됩니다. trimBefore={4 * fps}와 같이 JSX 노드에 표현식을 유지하면 Studio가 타이밍을 편집할 수 있고 컴포지션 프레임 레이트가 변경되어도 정확하게 유지됩니다.

useCurrentFrame()과 interpolate()를 사용하여 애니메이션을 구동하세요. CSS transition 또는 animation은 올바르게 렌더링되지 않으므로 리팩터링해야 합니다. Tailwind 애니메이션 클래스는 올바르게 렌더링되지 않으므로 리팩터링해야 합니다.

Easing.bezier() 및 Easing.spring()을 사용하여 타이밍을 사용자 정의하세요.

Remotion 상호 작용 모범 사례에 따라 마크업을 구성하세요. 편집 가능한 속성이 있는 씬의 경우 Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})를 선호하세요. 내부 마크업은 프래그먼트를 반환할 수 있습니다. 래퍼가 레이아웃 및 인스턴스 스타일을 처리합니다. 하단 서드와 같은 크기 조정 요소는 기본 레이아웃을 유지하고 자체 루트에 style을 전달할 수 있습니다. 재사용 가능한 씬을 연결된 컴포지션으로 등록하세요. 타이밍을 지원하는 컴포넌트에 직접 타이밍을 적용하고, 불필요한 <Sequence> 래퍼는 피하세요. premountFor를 지원하는 모든 타이밍 컴포넌트에 1초의 프리마운팅을 제공하세요: premountFor={fps}. 여기서 fps는 useVideoConfig()에서 가져옵니다. 미디어, 대화형 컴포넌트, <Sequence>, <TransitionSeries.Sequence>, <TransitionSeries.Overlay>에 적용하고, 씬 내부에 중첩된 타이밍 컴포넌트도 포함합니다. 중첩된 항목이 부모가 시작되기 전에 마운트되어야 하는 경우 부모 타임라인 항목도 프리마운트하세요. premountFor가 없는 컴포넌트(예: <TransitionSeries.Transition>)는 대체가 필요하지 않습니다.

Studio는 항목을 생성한 JSX 소스 노드를 편집합니다. 모든 컴포지션 등록, 클립, 씬, 레이어 및 시퀀스를 독립적으로 편집 가능한 자체 JSX 노드로 작성하고, 편집 가능한 속성을 인라인으로 배치하세요. 프로그래매틱 루프는 생성된 인스턴스가 의도적으로 하나의 소스 템플릿으로 제어될 때 적합하며, 사용자가 인스턴스를 개별적으로 편집해야 할 때는 적합하지 않습니다.

import { useCurrentFrame, useVideoConfig, Easing, interpolate, Interactive } from "remotion";

export const FadeIn = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  return (
    <Interactive.Div
      name="Title"
      premountFor={fps}
      style={{
        opacity: interpolate(frame, [0, 2 * fps], [0, 1], {
          extrapolateRight: "clamp",
          extrapolateLeft: "clamp",
          easing: Easing.bezier(0.16, 1, 0.3, 1),
        }),
      }}
    >
      Hello World!
    </Interactive.Div>
  );
};

interpolate() 호출을 style prop에 인라인으로 유지하세요. transform 대신 scale, translate, rotate CSS 속성을 사용하세요.

// 👍 인라인 편집 가능한 키프레임 및 변환 단축 속성
style={{
  scale: interpolate(frame, [0, 100], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
    output: 'perceptual-scale' // `scale` 애니메이션의 경우 "output: 'perceptual-scale'" 사용
  }),
  translate: interpolate(frame, [0, 100], ["0px 0px", "100px 100px"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
  rotate: interpolate(frame, [0, 100], ["20deg", "90deg"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
}}

// 👎 인라인이 아닌 값과 변환 문자열은 Studio에서 편집하기 어려워짐
const scale = interpolate(frame, [0, 100], [0, 1]);

style={{
  transform: `scale(${scale})`,
}}

에셋

프로젝트 루트의 public/ 폴더에 에셋을 배치하세요. public/ 폴더의 파일을 참조하려면 staticFile()을 사용하세요.

미디어 컴포넌트

비디오와 오디오는 @remotion/media의 <Video> 및 <Audio>를 사용하여 추가하세요. 이미지는 <CanvasImage> 컴포넌트를 사용하여 추가하세요. 애니메이션 GIF, APNG, WebP 또는 AVIF 이미지는 <AnimatedImage>를 사용하여 추가하고, Chrome을 사용하지 않는 경우 @remotion/gif를 사용하세요. public/의 파일에는 staticFile()을 사용하거나 원격 URL을 직접 전달하세요:

import { Audio, Video } from "@remotion/media";
import { staticFile, CanvasImage, AnimatedImage, useVideoConfig } from "remotion";

export const MyComposition = () => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video src={staticFile("video.mp4")} premountFor={fps} style={{ opacity: 0.5 }} />
      <Audio src={staticFile("audio.mp3")} premountFor={fps} />
      <CanvasImage
        src={staticFile("logo.png")}
        premountFor={fps}
        style={{ width: 100, height: 100 }}
      />
      <Video src="https://remotion.media/video.mp4" premountFor={fps} />
      <AnimatedImage src={staticFile('nyancat.gif')} premountFor={fps} />
    </>
  );
};

컴포지션이 주로 비디오 또는 오디오 클립의 타임라인인 경우 소스 구조를 선택하기 전에 video-editing.md를 읽으세요.

예제 씬

배경 비디오와 하단 서드로 구성됩니다. 하단 서드는 자체 타임라인이 있는 대화형 컴포넌트이며 연결된 컴포지션으로 등록됩니다. 텍스트는 children으로 전달되고 accentColor는 편집 가능한 속성입니다. 페이드 인은 호출 지점에서 인라인으로 키프레임 처리됩니다.

// MyScene.tsx
import { Video } from "@remotion/media";
import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
import { LowerThird } from "./LowerThird";

export const MyScene = () => {
  const { fps } = useVideoConfig();
  const frame = useCurrentFrame();

  return (
    <>
      <Video
        name="Background"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{
          position: "absolute",
          width: "100%",
          height: "100%",
        }}
      />
      <LowerThird
        name="Lower third"
        from={1 * fps}
        premountFor={fps}
        accentColor="#0b84f3"
        style={{
          opacity: interpolate(frame, [1 * fps, 2 * fps], [0, 1], {
            extrapolateRight: "clamp",
            extrapolateLeft: "clamp",
            easing: Easing.bezier(0.16, 1, 0.3, 1),
          }),
        }}
      >
        Jane Doe, Product Designer
      </LowerThird>
    </>
  );
};
// LowerThird.tsx
import type React from "react";
import { Interactive, type InteractivitySchema } from "remotion";

type LowerThirdProps = {
  readonly children: React.ReactNode;
  readonly accentColor: string;
  readonly style?: React.CSSProperties;
};

const LowerThirdInner: React.FC<LowerThirdProps> = ({
  children,
  accentColor,
  style,
}) => {
  return (
    <Interactive.Div
      style={{
        position: "absolute",
        left: 80,
        bottom: 80,
        display: "flex",
        alignItems: "center",
        gap: 20,
        backgroundColor: "white",
        borderRadius: 16,
        padding: "20px 32px",
        color: "black",
        fontFamily: "Helvetica, Arial, sans-serif",
        fontSize: 48,
        fontWeight: 600,
        ...style,
      }}
    >
      <div
        style={{
          width: 8,
          alignSelf: "stretch",
          borderRadius: 4,
          backgroundColor: accentColor,
        }}
      />
      {children}
    </Interactive.Div>
  );
};

const lowerThirdSchema = {
  ...Interactive.childrenSchema,
  accentColor: {
    type: "color",
    default: "#0b84f3",
    description: "포인트 색상",
  },
} as const satisfies InteractivitySchema;

export const LowerThird = Interactive.withSchema({
  Component: LowerThirdInner,
  componentName: "LowerThird",
  schema: lowerThirdSchema,
  wrapInSequence: true,
});
import { Composition } from "remotion";
import { LowerThird } from "./LowerThird";
import { MyScene } from "./MyScene";

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="MyScene"
        component={MyScene}
        durationInFrames={60}
        fps={30}
        width={1280}
        height={720}
      />
      <Composition
        id="LowerThird"
        component={LowerThird}
        durationInFrames={30}
        fps={30}
        width={1280}
        height={720}
        defaultProps={{
          children: "Jane Doe, Product Designer",
          accentColor: "#0b84f3",
        }}
      />
    </>
  );
};

지연 및 트리밍

타이밍을 지원하는 컴포넌트에 직접 타이밍을 적용하세요. 지원되는 속성, 예제 및 작업 순서는 타이밍 속성을 참조하세요.

지도

비디오에 지도를 포함하려면 Remotion Maps를 참조하세요.

텍스트 하이라이트 및 주석

텍스트 하이라이트(하이라이트 마커), 원, 밑줄, 취소선, 줄이 그어진 텍스트, 상자에 대해서는 text-highlights.md를 참조하세요.

다중 씬 비디오

여러 후속 씬이 있는 비디오를 계획하는 경우 multi-scene-video.md를 참조하세요.

연결된 컴포지션

씬 또는 레이어 그룹에 자체 편집 가능한 타임라인이 필요한 경우 connected-compositions.md를 따르세요. 다중 씬 비디오에서 중요한 씬에는 이 구조를 선호하세요.

사전 컴포지션 작업

Pre-compose Ambient glow (src/BarChart.tsx:134)와 같은 Studio 요청의 경우 주어진 위치에서 선택된 시퀀스 마크업을 찾으세요. connected-compositions.md에 따라 연결된 컴포지션을 만드세요: 마크업을 명명된 컴포넌트로 추출하고, 가능하면 Interactive.withSchema({wrapInSequence: true})로 대화형으로 만든 다음, 루트에 고유한 <Composition>과 함께 동일한 내보낸 컴포넌트 참조를 등록하세요. 대화형 컴포넌트를 타이밍 속성과 함께 직접 렌더링하거나, 래퍼에 목적이 있는 경우 시퀀스의 유일한 자식으로 렌더링하세요. 선택한 노드가 이미 시퀀스인 경우 해당 속성을 유지하고 자식을 추출하세요. 등록에는 치수, fps, duration 및 부모 사용과 동등한 defaultProps가 필요합니다. 등록된 컴포지션이 없는 컴포넌트 추출은 사전 컴포지션 요청을 완료하지 않습니다.

추출된 컴포넌트로 fontFamily 및 글꼴 로딩과 같은 상속된 스타일을 전하세요. 부모 독립성을 참조하세요.

음성 해설

ElevenLabs TTS를 사용하여 Remotion 컴포지션에 AI 생성 음성 해설을 추가하려면 voiceover.md를 참조하세요.

비디오 임베딩

비디오 임베딩(트리밍, 볼륨, 속도, 루핑, 피치)에 대한 고급 지식은 embedding-videos.md를 참조하세요.

오디오 임베딩

트리밍, 볼륨, 속도, 피치와 같은 고급 오디오 기능은 audio.md를 참조하세요.

크로핑

컴포넌트의 표시 사각형을 크롭해야 하는 경우 cropping.md를 참조하세요.

전환

씬 전환 패턴은 transitions.md를 참조하세요.

모션 블러

모션 블러 또는 움직임 궤적을 추가할 때는 motion-blur.md에서 선호하는 HTML-in-canvas 접근 방식, 미리보기 요구 사항 및 대안을 읽으세요.

시각적 및 픽셀 효과

시각 효과를 추가할 때 먼저 영향을 받아야 할 요소나 그룹을 식별하세요. 대상 컴포넌트가 effects를 지원하고 아래 기본 제공 효과가 요청과 일치하는 경우 CSS 근사치보다 해당 효과를 선호하세요. 예를 들어, 비네트가 해당 비디오에 영향을 미쳐야 하는 경우 <Video>의 effects prop에 vignette()를 직접 적용하세요.

다음 컴포넌트는 effects를 직접 허용합니다:

Import fromComponents
remotion<Img>, <CanvasImage>, <AnimatedImage>, <Solid>, <HtmlInCanvas>
@remotion/media<Video>
@remotion/gif<Gif>
@remotion/rive<RemotionRiveCanvas>
@remotion/shapes<Arrow>, <Callout>, <Circle>, <Ellipse>, <Heart>, <Pie>, <Polygon>, <Rect>, <Spark>, <Star>, <Triangle>

일반 HTML 요소, <Interactive.*> DOM 래퍼, <AbsoluteFill> 및 <Sequence>는 effects를 직접 허용하지 않습니다. 해당 콘텐츠에 효과를 적용하려면 <HtmlInCanvas>로 감싸고 래퍼에 effects prop을 배치하세요.

다른 요소의 경우 적합한 경우 일반 HTML 및 CSS를 선호하세요. 전체 그룹을 후처리하는 것이 적절하고 환경이 지원하는 경우 그룹을 <HtmlInCanvas>로 감싸는 것을 고려하세요.

효과를 적용하기 전에 effects.md를 읽어 가져오기, 매개변수 및 설정 방법을 확인하세요. 또한 일치하는 기본 제공 효과가 없을 때 createEffect()를 사용한 재사용 가능한 사용자 정의 효과에 대해서도 다룹니다.

사용 가능한 효과

@remotion/effects의 기본 제공 효과:

  • 색상 및 톤: brightness(), colorCorrection(), colorKey(), contrast(), duotone(), exposure(), grayscale(), hue(), invert(), levels(), linearGradient(), linearGradientTint(), lut(), saturation(), shadowsHighlights(), thermalVision(), tint(), vibrance(), whiteBalance().
  • 블러 및 궤적: blur(), lightTrail(), linearProgressiveBlur(), radialProgressiveBlur(), regionBlur().

Individual skills in this repo

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

관련 스킬