Figma 애니메이션을 Remotion으로 옮기기: JSON 파싱부터 GSAP까지
– ArtXTech
Figma 애니메이션을 코드로 옮기는 삽질기
Figma나 Jitter에서 만든 예쁜 애니메이션을 보고 “이거 코드로 똑같이 만들어보자!“라고 생각했던 적 있나?
시작은 단순했다
목표: Figma/Jitter → Remotion + GSAP 자동 변환
현실: 온갖 예상치 못한 버그와 삽질의 연속
애초 계획은 간단했다. 애니메이션 JSON을 파싱해서 GSAP 타임라인으로 변환하고, Remotion으로 비디오 렌더링까지 자동화하는 거였다. After Effects나 Lottie 없이도 웹 기술 스택만으로 고품질 애니메이션을 만들어보자는 야심.
그런데 막상 시작하니까… JSON 구조부터가 생각보다 복잡했다.
실제로 만든 것들
1. Countdown 애니메이션 컴포넌트
첫 번째로 만든 건 카운트다운 애니메이션이었다. 5개 레이어가 순차적으로 사라지면서 최종적으로 하나만 남는 구조였는데, 생각보다 까다로웠다.
// 각 레이어별 스케일과 타이밍
const layers = [
{ scale: '140% → 25%', hideTime: 0.7 },
{ scale: '45%', hideTime: 0.8 },
{ scale: '65%', hideTime: 0.9 },
{ scale: '85%', hideTime: 1.0 },
{ scale: '105%', hideTime: 1.12 }
];
// 그룹 레이어는 110% → 90%로 전체적으로 축소
문제는 레이어 스택킹이었다. 상위 레이어가 하위 레이어를 완전히 가려야 하는데, 텍스트 형태라서 투명한 부분이 있어서 뒤가 비쳐 보이는 거였다.
해결책: 배경색을 완전 불투명하게 설정하고 z-index를 명시적으로 지정했다.
.countdownLayer {
background-color: #FFDE02; // 완전 불투명 배경
z-index: var(--layer-index); // 레이어별 명시적 순서
}
2. Figma-Jitter JSON 파싱
두 번째는 본격적인 JSON 파싱이었다. “DON’T WORRY, BE HAPPY” 텍스트가 회전하고 스케일 변하고 마스크 효과까지 있는 복잡한 애니메이션이었다.
JSON 구조: 컴포지션 → 레이어 → 트랜스폼 → 키프레임
현실: 중첩된 객체 구조에서 필요한 데이터 찾기가 보물찾기 수준
가장 골치 아팠던 건 키프레임 데이터를 GSAP 타임라인으로 변환하는 부분이었다. Lottie/AE JSON 구조는 정말 복잡하게 되어 있더라.
// JSON에서 키프레임 추출
const keyframes = layer.transform.scale.keyframes;
// GSAP 프로퍼티로 매핑
tl.fromTo(element,
{ scale: keyframes[0].value },
{ scale: keyframes[1].value, duration: timing }
);
삽질 과정에서 만든 아키텍처
이런 저런 시행착오를 겪으면서 나름의 패턴이 생겼다.
공통 훅 시스템
// useSketchBase: 캔버스 설정과 스케일링 로직 추상화
const useSketchBase = (width, height) => {
return {
canvasStyle: { width, height },
containerStyle: { transform: `scale(${scaleFactor})` }
};
};
// useGsapTimeline: GSAP 타임라인 생성과 관리
const useGsapTimeline = () => {
const tl = useRef();
// 타임라인 설정 로직
return { timeline: tl.current, addAnimation };
};
프로젝트 구조
VideoEditPlayground/
├── src/remotion/
│ ├── Components/
│ │ └── useSketchBase.ts # 공통 캔버스 훅
│ ├── Sketches1920p/
│ │ ├── day40/
│ │ │ ├── day40Sketch.tsx # 텍스트 스크램블
│ │ │ └── sketchDay40.module.scss
│ │ └── countdown/
│ │ ├── CountdownSketch.tsx # 레이어 카운트다운
│ │ └── countdownSketch.module.scss
│ └── 100DaysOfMotion1/
│ └── day36/
│ ├── Index.tsx # JSON 변환 애니메이션
│ └── day36.module.scss
컴포넌트 관계 (Mermaid)
graph TD
A[Animation Component] --> B[useSketchBase Hook]
A --> C[useGsapTimeline Hook]
B --> D[Canvas Dimensions]
B --> E[Scaling Logic]
B --> F[Container Styles]
C --> G[GSAP Timeline]
C --> H[Animation Refs]
I[JSON Input] --> J[Parser]
J --> K[Transform Data]
K --> C
L[Designer Requirements] --> M[Component Props]
M --> A
만난 문제들과 해결법
레이어 가시성 문제
문제: 상위 레이어가 하위 레이어를 완전히 가리지 못함
원인: 텍스트 형태로 인한 투명 영역, z-index 미설정
해결: 배경색 불투명화 + 명시적 z-index 설정
타이밍 제어 이슈
문제: 점진적 fade가 아닌 즉시 hide가 필요했음
원인: tl.to() 대신 tl.set() 사용해야 함
해결: 정확한 시점에 즉시 숨김 처리
// 잘못된 방법
tl.to(layerRef.current, { opacity: 0, duration: 0.1 }, exactTime);
// 올바른 방법
tl.set(layerRef.current, { visibility: 'hidden' }, exactTime);
GSAP Timeline 패턴:
const animationBoxRef = useGsapTimeline<HTMLDivElement>(() => {
const tl = gsap.timeline();
// 애니메이션 로직
return tl;
});
JSON 구조 복잡성
문제: Lottie/AE JSON의 중첩 구조 해석이 어려움
해결: 체계적인 파싱 로직 구축
// 컴포지션 → 레이어 → 프로퍼티 순서로 파싱
const parseAnimation = (json) => {
const compositions = json.compositions;
return compositions.map(comp => ({
layers: comp.layers.map(parseLayer),
duration: comp.duration
}));
};
데이터 흐름도
[Figma/Jitter JSON]
↓ (parse)
[Animation Config]
↓ (props)
[Animation Component]
↓ (useSketchBase)
[Canvas Setup] + [Scaling Logic]
↓ (useGsapTimeline)
[GSAP Timeline]
↓ (execute)
[DOM Animation]
↓ (render)
[Remotion Video Output]
SOLVED ISSUES
- 레이어 가시성: 배경색 + z-index 조합
- 타이밍 제어: set() vs to() 구분 사용
- JSON 파싱: Lottie 구조 → GSAP 프로퍼티 매핑
기술 스택 조합의 묘미
Remotion + GSAP 조합이 생각보다 괜찮았다. Remotion은 비디오 렌더링에 최적화되어 있고, GSAP은 애니메이션 성능이 뛰어나니까 서로의 장점을 살릴 수 있었다.
// Remotion 컴포넌트 내에서 GSAP 사용
export const AnimationComponent = () => {
const { timeline } = useGsapTimeline();
useEffect(() => {
timeline
.fromTo('.element1', { scale: 0 }, { scale: 1, duration: 1 })
.to('.element2', { rotation: 360, duration: 2 }, '-=0.5');
}, []);
return <div className="animation-container">...</div>;
};
여기에 TypeScript까지 더해서 타입 안전성도 챙겼다. JSON 파싱할 때 타입 체크가 있으니까 런타임 에러 잡기가 훨씬 수월했다.
최종 목표와 현재 상황
애초 목표는 이거였다:
- Figma/Jitter 디자이너의 반복 작업 최소화
- After Effects/Lottie 대체 솔루션 구축
- 코드 기반 애니메이션 자동 생성
- 웹 기술 스택으로 고품질 비디오 제작
지금까지 기본적인 레이어 애니메이션과 간단한 JSON 파싱은 구현했다. 하지만 아직 갈 길이 멀다. 복잡한 패스 애니메이션이나 고급 이징 함수, 3D 트랜스폼 같은 건 아직 미지의 영역이다.
배운 점들
- JSON 구조 이해의 중요성: Lottie/AE JSON 스펙을 제대로 이해하지 않으면 파싱이 불가능.
- 레이어 관리의 복잡성: 단순해 보이는 레이어 스택킹도 실제로는 z-index, 투명도, 마스킹 등 고려할 요소가 많다.
- 타이밍의 정확성: 애니메이션에서 0.1초 차이도 전체 느낌을 바꿀 수 있다.
- 추상화의 필요성: 공통 로직을 훅으로 분리하니까 코드 재사용성이 크게 향상.
앞으로 해야 할 것들
- 더 복잡한 애니메이션 패턴 지원
- 자동 파싱 정확도 향상
- 성능 최적화 (특히 복잡한 애니메이션에서)
- 디자이너 친화적인 설정 인터페이스 구축
다른 리모션 포스팅을 보고 싶다면
2025-01-16-implementing-canvas-animation-to-remotion-kr
2025-03-18-making-my-own-video-editor-in-progress
KR-no-more-adobe-starting-remotion-kr
KR-remotion-gsap
KR-making-chart-data-journalism-storytelling-motion-setup-with-remotion
KR-converting-bodymovin-animation-to-remotion-template
KR-how-i-make-a-youtube-series-with-remotion-dynamic-srt-script-setup-exp-likejennie
KR-how-i-made-podcast-interview-with-ai-virtual-idol
KR-remotion-and-shader
KR-thepain-of-animation-debugging