이미지에서 콘텐츠로 전환
Overview
CSS 3D perspective로 10개 비디오 패널이 원형 캐러셀을 이루고, 패널 클릭 시 SVG 스트라이프 마스크로 시네마틱 콘텐츠 뷰가 전환되는 인터랙티브 무비 갤러리입니다. 드래그 관성 물리, Canvas 구체 파티클, SVG 레이팅 링까지 — 풀스크린 몰입형 경험을 하나의 훅에서 오케스트레이션합니다.
Feature-Sliced Design으로 캐러셀 전체 코드를 하나의 feature에 응집하고, 12개 UI 컴포넌트가 역할별로 분리되어 있습니다. 450줄 규모의 커스텀 훅이 드래그 · 오토플레이 · 콘텐츠 전환을 전담하고, UI 컴포넌트는 순수한 렌더링만 담당합니다.
Anatomy
UI 해부도
캐러셀은 8개의 독립 UI 컴포넌트로 구성됩니다. 3D perspective 안에서 10개 비디오 패널이 원형으로 배치되고, 클릭 시 스트라이프 마스크로 콘텐츠 뷰가 전환됩니다.
- LogoHeader
- 네온 펄스 로고 + 서브텍스트
- 3D Scene
- perspective 3D 캐러셀 컨테이너
- Preview Panels
- 10개 비디오 패널 + 반사 레이어
- ParticleCanvas
- Canvas 구체 파티클 배경
- NumberBar
- 드래그 연동 인디케이터 + 오토플레이
- Content Overlay
- 스트라이프 마스크 전환 상세 뷰
- RatingRings
- SVG 링 + 프로그레스 바 애니메이션
- BackButton
- SVG 라인 드로우 뒤로가기 버튼
Structure
프로젝트 아키텍처
Feature-Sliced Design 기반으로 캐러셀 관련 모든 코드를 하나의 feature 폴더에 응집합니다. 12개 UI 컴포넌트가 역할별로 분리되어 있고, 하나의 커스텀 훅이 전체 인터랙션을 전담합니다.
- items.json
- 10개 무비 아이템 (title · year · genre · video · ratings)
- useImageToContentAnimation.ts
- 450+ lines · 드래그 + 오토플레이 + 콘텐츠 전환 오케스트레이션
- SectionImageToContent.tsx
- 루트 컨테이너 · 훅 연결
- Carousel.tsx
- 3D perspective 캐러셀 + 배경
- Preview.tsx
- 개별 패널 (비디오 + 반사)
- Content.tsx
- 콘텐츠 상세 뷰 (비디오 배경)
- ContentInfo.tsx
- 제목 · 메타 · 설명 · 태그
- ContentOverlay.tsx
- 10개 콘텐츠 + BackButton 래퍼
- RatingRings.tsx
- SVG 링 + 프로그레스 바
- NumberBar.tsx
- 하단 인디케이터 + 오토플레이 버튼
- LogoHeader.tsx
- 네온 펄스 로고
- BackButton.tsx
- SVG 라인 드로우 버튼
- ParticleCanvas.tsx
- Canvas 구체 파티클 1400개
- StripeClipDefs.tsx
- SVG clipPath 스트라이프 8칸
- image-to-content.css
- 로딩 오버레이 · 패널 글로우 · 넘버바 · 콘텐츠 마스크
Data Flow
컴포넌트 데이터 플로우
데이터는 단방향으로 흐릅니다. JSON에서 시작해 커스텀 훅이 드래그 · 오토플레이 · 전환 로직을 처리하고, UI 컴포넌트는 순수하게 렌더링만 담당합니다.
ANIMATION OUTPUT
- 3D Carousel
- 원형 회전
- rotateY + translateZ + parallax
- Stripe Mask
- 콘텐츠 전환
- SVG clipPath 8칸 stagger
- SplitType
- 텍스트 리빌
- lines yPercent stagger 60ms
- Rating Rings
- 데이터 시각화
- SVG strokeDashoffset + scaleX
Timeline
애니메이션 타임라인
콘텐츠 열기는 약 4.5초 동안 11개의 애니메이션 레이어가 정밀하게 오케스트레이션됩니다. 패널 퇴장 → 스트라이프 마스크 → 텍스트 리빌 → 데이터 시각화 순서로 진행됩니다.
- 클릭 패널 기준 주변으로 퍼지며 페이드아웃
- 넘버바 하단으로 슬라이드 + 투명도 0
- 뒷면 패널 포함 전체 opacity 0
- 콘텐츠 비디오 재생 시작
- SVG 스트라이프 8칸 시간차 하강 (stagger 60ms)
- 시네마틱 그라디언트 + 하단 블러 마스크
- SplitType 타이틀 yPercent 리빌 (stagger -120ms)
- 메타 정보 + 본문 lines 순차 진입
- 장르 태그 배지 yPercent 페이드인
- SVG 링 strokeDashoffset + 바 scaleX 애니메이션
- SVG 라인 드로우 + 텍스트 xPercent 진입
Interaction
인터랙션 플로우
사용자 인터랙션은 Drag/Inertia, Panel Click, Back Click 세 갈래로 분기됩니다. 드래그 임계값(5px)으로 클릭과 드래그를 구분하고, Panel Click은 스트라이프 마스크로 콘텐츠를 전환합니다.
- 1
pointerDown → startAngle 기록
- 2
pointerMove → 실시간 회전
angle -= dx * 0.125 - 3
pointerUp → 관성 적용
velocity * 50 (max ±120°)
- 1
dx < 5px → 클릭 판정
CLICK_THRESHOLD = 5 - 2
패널 hit-test (3D fallback 포함)
- 3
보이는 패널 순차 퇴장
circular distance stagger 120ms - 4
스트라이프 마스크 → 콘텐츠 리빌
- 1
Back 버튼 라인 수축
- 2
텍스트 · 레이팅 퇴장
- 3
스트라이프 역방향 → 캐러셀 복원
sorted panels fadeIn stagger
Patterns
핵심 기술 패턴
단순히 모션이 예쁜 것이 아니라, 프로덕션에서 유지보수 가능한 구조로 설계했습니다. 3D CSS 캐러셀 · SVG 스트라이프 마스크 · Canvas 파티클 · 포인터 드래그 물리까지 — 실무에서 바로 적용할 수 있는 패턴입니다.
- CSS 3D perspective 캐러셀
- perspective(1100px) 안에서 10개 패널이 rotateY + translateZ로 원형 배치 — 드래그로 scene 전체를 회전시키고 각 패널에 parallax 오프셋 적용
- panels: rotateY(var(--panel-angle)) translateZ(calc(var(--itc-radius) * -1)) scene: gsap.set(scene, { rotateY: angle })
- SVG clipPath 전환 효과
- 8칸 SVG rect를 clipPathUnits='objectBoundingBox'로 정의하고, y속성을 -1 → 0 stagger 애니메이션하여 콘텐츠를 순차 노출
- <clipPath clipPathUnits="objectBoundingBox"> <rect y="-1" width="0.127" height="1" /> <!-- ×8 stripes, stagger 60ms -->
- 텍스트 라인 분할 애니메이션
- lines 단위로 텍스트를 분할하고 overflow:hidden 래퍼로 감싼 뒤 yPercent로 순차 리빌 — resize 이벤트에서 재분할 처리
- new SplitType(el, { types: 'lines' }) wrapLines(lines, 'div', 'oh') gsap.to(lines, { yPercent: 0, stagger: 0.06 })
- Canvas 3D 구체 파티클
- 1400개 파티클이 구체 표면에 분포 — latitude ring 집중 배치, 림 라이팅, 펄스 불투명도로 깊이감 연출
- x3d = sin(phi) * cos(theta) * r y3d = cos(phi) * r rimFactor = 1 + (1 - |z3d|/r) * 3.0
- 포인터 드래그 물리 시뮬레이션
- pointerEvents 기반 드래그로 velocity 추적 — release 시 관성(velocity×50) 적용, power3.out easing으로 자연스러운 감속
- velocity = (clientX - lastX) / dt inertia = clamp(velocity * 50, ±120°) gsap.to(drag, { angle: target, ease: 'power3.out' })
- 비디오 프리로드 + 로딩 상태
- 패널 비디오의 loadeddata 이벤트를 Promise로 래핑 — 전체 프리로드 완료 후 loading 오버레이 제거, hover/content 진입 시 play/pause 전환
- preloadVideos(panels).then(() => { body.classList.remove('loading') }) // content: data-video-src → .src 지연 할당
Responsive
반응형 전략
데스크탑 퍼스트로 설계하고, CSS 변수로 캐러셀 perspective와 패널 사이즈를 제어합니다. 모바일에서는 perspective를 700px로 축소하고, Canvas 파티클을 20%로 줄여 성능을 확보합니다.
- perspective(1100px) 3D 캐러셀
- 패널 380×25svh 사이즈
- 1400개 Canvas 파티클
- 패널 hover 글로우 + 비디오 재생
- 콘텐츠 좌우 2컬럼 (info + ratings)
- perspective 유지 · 패널 사이즈 동일
- 콘텐츠 하단 여백 축소
- 레이팅 링 크기 유지
- BackButton 위치 right-8
- 넘버바 너비 85% 유지
- perspective(700px)로 축소
- 패널 220×28svh 사이즈
- 파티클 수 280개 (20%)
- 콘텐츠 세로 스크롤 레이아웃
- 레이팅 가로 배치 · 링 축소