Next.js · GSAP · SplitType · Canvas

이미지에서 콘텐츠로 전환


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 컴포넌트가 역할별로 분리되어 있고, 하나의 커스텀 훅이 전체 인터랙션을 전담합니다.

features/image-to-contentFSD
data/1
items.json
10개 무비 아이템 (title · year · genre · video · ratings)
hooks/1
useImageToContentAnimation.ts
450+ lines · 드래그 + 오토플레이 + 콘텐츠 전환 오케스트레이션
ui/12
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칸
styles/1
image-to-content.css
로딩 오버레이 · 패널 글로우 · 넘버바 · 콘텐츠 마스크

Data Flow

컴포넌트 데이터 플로우

데이터는 단방향으로 흐릅니다. JSON에서 시작해 커스텀 훅이 드래그 · 오토플레이 · 전환 로직을 처리하고, UI 컴포넌트는 순수하게 렌더링만 담당합니다.

DATA
items.json10개 무비 객체title · video · ratings
HOOK
useImageToContentAnimationDrag + InertiaAutoplay TickerSplitType
UI
SectionImageToContentCarousel (3D Scene)Preview ×10Content ×10

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.0s0.5s1.0s1.5s2.0s2.5s3.0s3.5s4.0s4.5s
Panel Spread
NumBar Out
Panels Fade
Video Start
Stripe Reveal
Overlay + Blur
Title In
Meta + Text
Tags In
Rating Rings
Back Button
클릭 패널 기준 주변으로 퍼지며 페이드아웃
넘버바 하단으로 슬라이드 + 투명도 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은 스트라이프 마스크로 콘텐츠를 전환합니다.

POINTER EVENT
Drag / InertiaDRAG
  1. 1

    pointerDown → startAngle 기록

  2. 2

    pointerMove → 실시간 회전

    angle -= dx * 0.125
  3. 3

    pointerUp → 관성 적용

    velocity * 50 (max ±120°)
Panel ClickADVANCED
  1. 1

    dx < 5px → 클릭 판정

    CLICK_THRESHOLD = 5
  2. 2

    패널 hit-test (3D fallback 포함)

  3. 3

    보이는 패널 순차 퇴장

    circular distance stagger 120ms
  4. 4

    스트라이프 마스크 → 콘텐츠 리빌

Back ClickREVERSE
  1. 1

    Back 버튼 라인 수축

  2. 2

    텍스트 · 레이팅 퇴장

  3. 3

    스트라이프 역방향 → 캐러셀 복원

    sorted panels fadeIn stagger
isAnimating 가드
전환 완료isAnimating = false → 새 입력 수신 가능
입력 차단isAnimating = true → 모든 포인터 무시

Patterns

핵심 기술 패턴

단순히 모션이 예쁜 것이 아니라, 프로덕션에서 유지보수 가능한 구조로 설계했습니다. 3D CSS 캐러셀 · SVG 스트라이프 마스크 · Canvas 파티클 · 포인터 드래그 물리까지 — 실무에서 바로 적용할 수 있는 패턴입니다.

3D Carousel
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 })
Stripe Mask
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 -->
SplitType
텍스트 라인 분할 애니메이션
lines 단위로 텍스트를 분할하고 overflow:hidden 래퍼로 감싼 뒤 yPercent로 순차 리빌 — resize 이벤트에서 재분할 처리
new SplitType(el, { types: 'lines' }) wrapLines(lines, 'div', 'oh') gsap.to(lines, { yPercent: 0, stagger: 0.06 })
Particle Canvas
Canvas 3D 구체 파티클
1400개 파티클이 구체 표면에 분포 — latitude ring 집중 배치, 림 라이팅, 펄스 불투명도로 깊이감 연출
x3d = sin(phi) * cos(theta) * r y3d = cos(phi) * r rimFactor = 1 + (1 - |z3d|/r) * 3.0
Drag + Inertia
포인터 드래그 물리 시뮬레이션
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' })
Video Preload
비디오 프리로드 + 로딩 상태
패널 비디오의 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%로 줄여 성능을 확보합니다.

Desktop1024px+
  • perspective(1100px) 3D 캐러셀
  • 패널 380×25svh 사이즈
  • 1400개 Canvas 파티클
  • 패널 hover 글로우 + 비디오 재생
  • 콘텐츠 좌우 2컬럼 (info + ratings)
Tabletmax-xl~1024px
  • perspective 유지 · 패널 사이즈 동일
  • 콘텐츠 하단 여백 축소
  • 레이팅 링 크기 유지
  • BackButton 위치 right-8
  • 넘버바 너비 85% 유지
Mobilemax-md~768px
  • perspective(700px)로 축소
  • 패널 220×28svh 사이즈
  • 파티클 수 280개 (20%)
  • 콘텐츠 세로 스크롤 레이아웃
  • 레이팅 가로 배치 · 링 축소