발표자료 기반으로 그래프·픽토그램이 움직이는 영상을 코드로 생성하기 위한 학습·제작 레퍼런스. 프롬프트·프로젝트 코드 전문·개념별 실습·커리큘럼을 한 곳에 담았다. 아래 라이브 데모 자체가 원칙(이징·스태거·stroke 애니)을 시연한다.
숫자·데이터 → 화면 요소를 코드로 매핑. "숫자만 바꿔 양산"이 가능한 지점이 자동화의 핵심.
영상이 텍스트(코드)라 diff·리뷰·재사용 가능. 템플릿 한 번 만들면 데이터만 갈아끼운다.
기술보다 이징·스태거·타이밍이 "싸구려 vs 프로"를 결정. 이 보드가 그 원칙을 코드로 보여준다.
아래는 실제 BarRace.tsx(Remotion) 산출물을 순수 CSS로 재현한 것. 이징·스태거·stroke 애니가 그대로 보인다.
PPT 수치·메시지를 숫자 배열/JSON으로 정리
Remotion에서 차트·픽토를 코드로. interpolate로 애니
npm run studio로 타임라인 확인·수정
npm run render → mp4 (자체 ffmpeg)
CapCut에서 BGM·최종 컷·자막
Node 18+ (권장 v20/v24), npm. Remotion은 자체 ffmpeg를 번들하므로 시스템 ffmpeg 불필요.
# 1) 공식 템플릿으로 새 프로젝트 (대화형)
npx create-video@latest
# 2) 또는 우리 스타터 폴더에서 바로
cd motion_video_team/starter
npm install
# 3) 스튜디오(미리보기) — 타임라인을 눈으로 보며 수정
npm run studio # = remotion studio
# 4) mp4 렌더 (out/starter.mp4 생성, 첫 실행 시 Headless Chrome 1회 다운로드)
npm run render # = remotion render BarRace out/starter.mp4
# 5) 확장 라이브러리(차트/픽토/폰트)
npm i @visx/scale @visx/shape lucide-react @remotion/google-fonts
starter/out/starter.mp4).스타터 프로젝트 전체. 각 파일의 [개념N] 주석이 아래 6번 개념 섹션과 연결된다.
{
"name": "motion-starter",
"version": "0.1.0",
"private": true,
"scripts": {
"studio": "remotion studio",
"render": "remotion render BarRace out/starter.mp4"
},
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1",
"remotion": "^4.0.0"
},
"devDependencies": {
"@remotion/cli": "^4.0.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"typescript": "^5.4.0"
}
}
// Remotion 진입점: 루트 컴포넌트를 등록한다. (렌더러/스튜디오가 여기서 시작)
import { registerRoot } from "remotion";
import { RemotionRoot } from "./Root";
registerRoot(RemotionRoot);
import { Composition } from "remotion";
import { BarRace } from "./BarRace";
// [개념4 · 데이터매핑] defaultProps의 data 배열만 바꾸면 영상이 바뀐다.
// 여기가 "숫자만 바꿔서 영상 양산"의 진입점.
export const RemotionRoot: React.FC = () => {
return (
<Composition
id="BarRace"
component={BarRace}
durationInFrames={120} // [개념1] 30fps × 4초 = 120프레임
fps={30}
width={1280}
height={720}
defaultProps={{
title: "분기 성장 지표",
data: [
{ label: "1월", value: 42 },
{ label: "2월", value: 68 },
{ label: "3월", value: 91 },
],
}}
/>
);
};
import { AbsoluteFill, interpolate, Easing, useCurrentFrame } from "remotion";
/**
* 모션영상팀 스타터 예제 — 교재용.
* 발표자료형 데이터 모션그래픽의 5개 핵심 개념을 한 화면에서 보여준다.
* 각 개념이 코드 어디에 있는지 [개념N] 주석으로 표시했다.
*/
// [개념4 · 데이터매핑] 화면에 그려질 데이터. 이 배열만 바꾸면 영상이 바뀐다.
type Datum = { label: string; value: number };
type Props = { title: string; data: Datum[] };
// ── 디자인 토큰 (배열 기본기) ─────────────────────────────
// [색 60-30-10] 배경=BG(60), 축/보조=SUB(30), 강조=ACCENT(10, 딱 하나)
const BG = "#0f172a";
const SUB = "#334155";
const TEXT = "#e2e8f0";
const ACCENT = "#38bdf8";
// [안전영역] 화면 가장자리에서 8~10% 안쪽에만 요소를 둔다.
const W = 1280;
const H = 720;
const MARGIN_X = 160; // 12.5%
const BASELINE_Y = 600; // 바가 서는 바닥선
const MAX_BAR_H = 380; // value=100일 때 최대 높이
const MAX_VALUE = 100;
export const BarRace: React.FC<Props> = ({ title, data }) => {
// [개념1 · 타임라인] 지금 몇 번째 프레임인지. 모든 애니의 시간 기준.
const frame = useCurrentFrame();
const slotW = (W - MARGIN_X * 2) / data.length; // 바 하나가 차지하는 가로 폭
const barW = 120;
// [개념3 · SVG stroke 애니] 제목 옆 "상승 화살표"가 그려지듯 등장.
// pathLength=100으로 경로 길이를 100으로 정규화 → dashoffset 100→0 이면 "그려짐".
const drawProgress = interpolate(frame, [10, 40], [100, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic), // [개념2 · 이징]
});
return (
<AbsoluteFill style={{ backgroundColor: BG, fontFamily: "sans-serif" }}>
{/* 제목 — [시각적 위계] 가장 큰 텍스트 = 주인공 */}
<div
style={{
position: "absolute", top: 70, left: MARGIN_X,
color: TEXT, fontSize: 56, fontWeight: 700,
display: "flex", alignItems: "center", gap: 20,
}}
>
{title}
{/* 자기 자신을 그리는 상승 화살표 */}
<svg width={90} height={70} viewBox="0 0 90 70">
<polyline
points="10,55 35,35 55,45 80,15"
fill="none" stroke={ACCENT} strokeWidth={6}
strokeLinecap="round" strokeLinejoin="round"
pathLength={100} strokeDasharray={100} strokeDashoffset={drawProgress}
/>
<polyline
points="80,15 80,32 63,15 80,15"
fill={ACCENT} stroke={ACCENT} strokeWidth={2}
opacity={interpolate(frame, [38, 46], [0, 1], {
extrapolateLeft: "clamp", extrapolateRight: "clamp",
})}
/>
</svg>
</div>
<svg width={W} height={H} style={{ position: "absolute", inset: 0 }}>
{/* 바닥 축선 — [보조색 30] */}
<line x1={MARGIN_X - 20} y1={BASELINE_Y} x2={W - MARGIN_X + 20} y2={BASELINE_Y}
stroke={SUB} strokeWidth={2} />
{data.map((d, i) => {
const cx = MARGIN_X + slotW * i + slotW / 2; // 슬롯 중앙 정렬
// [스태거] 바마다 등장을 3프레임(0.1초)씩 뒤로 민다 → 순차 등장.
const delay = 20 + i * 3;
// [개념1+2] 0→1 성장 진행도에 이징 적용 (등속 금지).
const grow = interpolate(frame, [delay, delay + 22], [0, 1], {
extrapolateLeft: "clamp", extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
const fullH = (d.value / MAX_VALUE) * MAX_BAR_H;
const barH = fullH * grow; // 실제 그려질 높이
const y = BASELINE_Y - barH;
return (
<g key={d.label}>
<rect x={cx - barW / 2} y={y} width={barW} height={barH} rx={10} fill={ACCENT} />
{/* 수치 — [게슈탈트 근접] 바 바로 위에 붙여 한 덩어리로 */}
<text x={cx} y={y - 16} fill={TEXT} fontSize={34} fontWeight={700}
textAnchor="middle" opacity={grow}>
{Math.round(d.value * grow)}
</text>
{/* 라벨 — 바 아래, 축과 근접 */}
<text x={cx} y={BASELINE_Y + 44} fill={TEXT} fontSize={30} textAnchor="middle">
{d.label}
</text>
</g>
);
})}
</svg>
</AbsoluteFill>
);
};
각 개념은 "동작 원리 → 코드 위치 → 직접 바꿔볼 것" 3단. 레버리지(가성비) 순서.
프레임/fps가 시간 축. interpolate(frame, [시작,끝], [값0,값1])은 frame이 시작→끝으로 갈 때 값0→값1로 선형 변환. extrapolate…:"clamp"로 범위 밖을 고정한다.
const frame = useCurrentFrame(); // 0,1,2,… (30fps면 1초=30)
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: "clamp", // 20프레임 뒤엔 1로 고정
}); // 0→20프레임(≈0.66초) 동안 서서히 등장[delay, delay+22]의 22를 40으로 늘리면 바가 더 천천히 자란다. fps로 나눠 "몇 초"인지 감을 잡자(22/30 ≈ 0.73초).선형(linear)은 싸구려로 보인다. 위 보간값에 easing: Easing.out(Easing.cubic)를 얹으면 "빠르게 시작→부드럽게 감속". 물리 기반 등장은 spring().
// (A) 이징 얹기
const grow = interpolate(frame, [20, 42], [0, 1], {
easing: Easing.out(Easing.cubic), extrapolateRight: "clamp",
});
// (B) 스프링 — 통통 튀는 자연스러운 등장
const s = spring({ frame, fps: 30, config: { damping: 12 } }); // 0→1easing 줄을 지우고 렌더해 등속과 비교해보라. 한 줄 차이로 "프로 vs 아마추어"가 갈린다.pathLength={100}으로 경로 길이를 100으로 정규화하고, strokeDasharray={100} + strokeDashoffset를 100→0으로 보간하면 선이 "그려진다". Lucide 아이콘 대부분이 stroke path라 이 기법이 바로 먹힌다.
const draw = interpolate(frame, [10, 40], [100, 0], { extrapolateRight: "clamp" });
<polyline points="10,55 35,35 55,45 80,15" fill="none" stroke={ACCENT} strokeWidth={6}
pathLength={100} strokeDasharray={100} strokeDashoffset={draw} />points를 다른 폴리라인(예: 체크마크 "20,50 40,70 80,25")으로 바꾸면 다른 아이콘이 그려진다.value / MAX_VALUE * MAX_BAR_H로 수치를 픽셀 높이로 환산. Root.tsx의 data 배열이 유일한 입력이다. 여기가 "숫자만 바꿔 양산"의 진입점.
const fullH = (d.value / MAX_VALUE) * MAX_BAR_H; // 91 → 345.8px
const barH = fullH * grow; // 성장 진행도(0→1) 곱data에 {label:"4월", value:77}를 추가하면 바가 자동으로 하나 늘고 slotW가 재계산돼 배치가 맞춰진다.remotion render <컴포지션ID> <출력경로>로 mp4 출력. Remotion이 자체 ffmpeg를 번들해 시스템 설치가 불필요. CapCut은 BGM·최종 컷 마무리용.
npx remotion render BarRace out/starter.mp4
npx remotion render BarRace out/hd.mp4 --scale=1.5 # 해상도 1.5배
npx remotion render BarRace out/clip.mp4 --frames=0-60 # 구간만--frames=0-60으로 앞 2초만 뽑아 빠르게 확인. 최종은 전체 렌더.Claude Code(또는 아무 Claude 세션)에 붙여넣어 팀을 세우고 영상을 만드는 실전 프롬프트. <…> 부분만 네 데이터로 바꾸면 된다.
너는 "모션영상 스튜디오" 팀이다. 발표자료 기반 데이터 모션그래픽 영상을
GUI 편집이 아니라 코드(Remotion)로 만들고, 그 과정을 나에게 가르친다.
[고정 스택] Remotion(React/TS) + visx(차트) + lucide-react(픽토) + @remotion/google-fonts
[핵심 API] useCurrentFrame() · interpolate() · spring()
[역할] 디렉터(총괄) · 비주얼라이저(데이터→도형) · 모션디자이너(움직임+배열) ·
렌더엔지니어(mp4/ffmpeg/CapCut) · 튜터(개념 설명)
[불변 규칙]
1. 데이터는 내가 준 실제 값만 쓴다 — 수치를 지어내지 않는다.
2. 모든 애니는 이징 필수(등속 금지) · 스태거 0.1초 · 전환 0.3~0.6초.
3. 색 60-30-10(강조색 하나) · 폰트 최대 2종 · 가장자리 8~10% 안전영역.
4. 새 애니/배열에는 "어떤 원칙을 적용했는지" 한 줄 주석을 남긴다(학습용).
5. 완료 전 실제 렌더를 1회 실행하고 산출 경로·용량을 보고한다. 못 하면 "미검증"이라 쓴다.
준비되면 "무슨 데이터를 영상으로 만들까?"라고 먼저 물어라.
아래 데이터로 4초(30fps·1280×720) 바차트 모션 영상을 Remotion으로 만들어줘.
제목: "<제목 예: 2026 상반기 매출>"
데이터:
- <라벨1>: <값1>
- <라벨2>: <값2>
- <라벨3>: <값3>
강조 메시지: "<한 문장 예: 3분기 연속 성장>"
요구사항:
- 바는 바닥에서 목표 높이로 easeOut 성장, 0.1초씩 스태거 등장
- 각 바 위에 실시간으로 올라가는 수치 표시
- 제목 옆 상승 화살표를 stroke로 그려지듯 등장(pathLength 트릭)
- 색은 60-30-10, 강조색 하나. 안전영역 지킬 것
- data 배열만 바꾸면 재사용되도록 Root.tsx에 분리
끝나면 npm run render로 mp4 뽑고 경로 알려줘.
빈 폴더에 Remotion 스타터 프로젝트를 만들어줘. 파일 4개만:
package.json / src/index.ts / src/Root.tsx / src/BarRace.tsx
- BarRace: props로 title과 {label,value}[] 데이터를 받는 바차트 컴포넌트
- 개념 1~5(보간·이징·SVG stroke·데이터매핑·렌더)를 각각 [개념N] 주석으로 표시
- Root.tsx에 defaultProps로 샘플 데이터 3개
- scripts: "studio": "remotion studio", "render": "remotion render BarRace out/starter.mp4"
- 의존성: react/react-dom 18, remotion ^4, @remotion/cli, typescript
만든 뒤 npm install → npm run render로 out/starter.mp4까지 검증하고
각 파일이 무슨 역할인지 3줄로 설명해줘.
이 컴포넌트의 움직임을 모션 디자이너 관점에서 리뷰해줘.
- linear 이징이 있으면 easeOut/spring으로 교정
- 동시 등장을 0.1초 스태거로
- 전환이 0.3~0.6초 밖이면 조정
- "한 번에 하나씩" 위반 지점 지적
바꾼 부분마다 어떤 원칙을 적용했는지 한 줄 주석을 남겨줘.
지금 순수 SVG 바차트를 visx(@visx/scale,@visx/shape)로 바꿔줘.
- scaleLinear로 값→픽셀, scaleBand로 x배치
- 라이브러리 자체 애니는 끄고(isAnimationActive 개념), 움직임은
전부 Remotion interpolate로 프레임 제어
- 같은 data props 형태 유지(호환)
라인차트 버전도 하나 더 만들어서 stroke self-draw로 그려지게 해줘.
interpolate가 정확히 뭘 하는지, 이 프로젝트 코드 위치를 짚어가며 초보자에게 설명하고, 내가 직접 바꿔볼 것 3개를 줘."interpolate로. 바 높이·라인 길이를 프레임값에 매핑한다. (라이브러리 자체 애니는 끈다)import { TrendingUp } from "lucide-react";
// Lucide 아이콘은 내부가 stroke path → dashoffset 트릭이 바로 먹힘
const draw = interpolate(frame, [0, 25], [1, 0], { extrapolateRight: "clamp" });
<TrendingUp color={ACCENT} size={64} strokeWidth={2.5}
style={{ strokeDasharray: 120, strokeDashoffset: 120 * draw }} />import { loadFont } from "@remotion/google-fonts/Pretendard";
const { fontFamily } = loadFont();
// style={{ fontFamily }} — 렌더 시 폰트 누락 방지(안정적 로드)import { scaleLinear, scaleBand } from "@visx/scale";
const x = scaleBand({ domain: data.map(d => d.label), range: [MARGIN_X, W - MARGIN_X], padding: 0.4 });
const y = scaleLinear({ domain: [0, MAX_VALUE], range: [BASELINE_Y, 100] });
// 모양은 visx 스케일로, 성장(0→1)은 Remotion interpolate로 곱한다
const grow = interpolate(frame, [delay, delay + 22], [0, 1], { easing: Easing.out(Easing.cubic), extrapolateRight: "clamp" });
const barH = (BASELINE_Y - y(d.value)) * grow;| 프레임워크 | 언어 | 강점 | 추천 대상 |
|---|---|---|---|
| Remotion ★ 채택 | React/TS | 발표자료형 슬라이드+차트+픽토, 웹 기술 그대로 → mp4 | 슬라이드 느낌 영상 (본 프로젝트) |
| Motion Canvas | TypeScript | 순수 모션그래픽, 다이어그램·화살표·강조 | 기술 설명 영상 |
| Manim | Python | 수식·그래프 애니(3Blue1Brown 스타일) | 그래프 중심, 파이썬 선호 |
"발표자료 기반 + 그래프/픽토그램"이면 Remotion이 최적. HTML/CSS/SVG를 그대로 쓰므로 슬라이드 레이아웃이 자연스럽고 mp4로 렌더된다.
배열이 어색한 건 감이 아니라 원칙 부재. 4개만 잡으면 된다.
① 그리드·정렬 여백 8~10% 안전영역, 선에 맞춤(눈대중 금지). ② 시각적 위계 크기·굵기·색으로 서열, 주인공 하나. ③ 여백 다 채우지 말 것, 8px 배수. ④ 게슈탈트 근접·유사 라벨-수치 묶기.
색 60-30-10 배경60 / 보조30 / 강조10, 강조색 딱 하나(이 보드는 ■ 하나). 폰트 최대 2종, 배수 스케일(16→24→36→54). 영상 특화: 폰트 크게 + 가장자리 세이프영역.
등속(linear) 금지. easeInOut/spring 기본. 이거 하나로 결과물이 프로처럼.
바 5개 동시 등장 = 촌스러움. 0.1초씩 밀어 순차 등장.
시선 유도. 동시에 여러 개가 움직이면 정신없다.
대부분 전환은 이 구간. 너무 빠르면 안 읽히고 느리면 지루.
왼쪽에서 들어오면 왼쪽으로 나간다. 규칙 통일.
이 중 slow in/out(이징)·스태거·anticipation 3개가 데이터 모션의 핵심.
레버리지(가성비) 순서. 각 단계는 "개념 → 코드 위치 → 바꿔볼 것" 3단.
| 단계 | 배우는 것 | 코드 위치 · 실습 | 상태 |
|---|---|---|---|
| 1 | 타임라인 & 보간 | useCurrentFrame·interpolate / 성장 구간 늘려보기 | ✅ 실습 |
| 2 | 이징 (가성비 최고) | Easing.out(cubic)·spring / 이징 빼고 비교 | ✅ 실습 |
| 3 | SVG 기초 (stroke) | 화살표 self-draw / points 바꿔보기 | ✅ 실습 |
| 4 | 데이터 → 시각화 매핑 | Root.tsx의 data / 항목 추가 | ✅ 순수 SVG · ⏭ visx 확장 |
| 5 | 렌더 파이프라인 | remotion render / 구간 렌더 | ✅ mp4 검증 |