시네마틱 스크롤스토리텔링
기본 아키텍처 (필수)
- 단일 핀, 단일 타임라인. 히어로(또는 주요 챕터)는 N개의 뷰포트 높이 동안 지속되는 컨테이너를 고정하고, 진행률 0→1이 모든 것을 제어합니다. 여러 스크롤 애니메이션이 경쟁한다면, 그것은 단순한 효과가 있는 일반 웹일 뿐입니다.
- 절대 스크롤 하이재킹 금지. 사용자는 네이티브 스크롤로 속도를 제어하며, 애니메이션은 '스크러빙'됩니다(
scrub: true). 휠을 가로채거나 사용자 대신 스크롤을 움직이는 것은 금지됩니다. - 단일 애니메이션 루프. Lenis는 GSAP 티커(
autoRaf: false)와 ScrollTrigger에 통합되어 동기화됩니다. 또는 네이티브 스크롤을 scrub과 함께 사용합니다. 두 개의 루프가 경쟁하는 것은 잔상(jank)의 주된 원인이며, 에셋 최적화로 해결되지 않습니다. - 텍스트는 DOM에 존재하며, 캔버스 위에 오버레이되고 진행률에 따라 레이어가 나뉩니다. 절대 캔버스 내에서 텍스트를 렌더링하지 마십시오.
프레임 시퀀스 (Apple 스타일 기법)
- 선호하는 소스: 실제 비디오 →
ffmpeg로 프레임 추출(fps=12..24, 화면 해상도로 크기 조정). WebP q≈68-75로 내보내고 직관이 아닌 결과를 보며 미세 조정합니다. - 최대 ~90-120 프레임; 모바일에서는 절반 또는 720p 버전을 제공합니다.
- 슬라이딩 윈도우 방식으로 로드: 현재 프레임 주변 ±6-10 프레임만 유지; 전체 시퀀스를 디코딩하지 않습니다(1080p 전체 시퀀스는 iOS Safari를 죽입니다).
IntersectionObserver(rootMargin ~800px),requestIdleCallback을 사용한 디코드, 히어로를 제외한fetchPriority="low"로 사전 로드합니다.- 캔버스는
devicePixelRatio를 1.5로 제한하고cover/contain을 계산하여 사용; 디코딩된 프레임만 그리며, 정확한 프레임이 없으면 이미 로드된 가장 가까운 프레임을 사용합니다(절대 검은색으로 그리지 않음). - 불변 캐시(
Cache-Control: public, max-age=31536000, immutable)와 버전 관리된 파일 이름을 사용합니다. prefers-reduced-motion및 느린 연결에 대한 폴백: 정적 포스터 + 핀이 없는 일반 챕터.- 후처리가 필요 없는 더 간단한 대안: 프레임별로
<img>의src를 교체(모든 브라우저에서 안정적, 캔버스 불필요).
비디오 스크러빙 (<video> 사용 시)
currentTime은 단일requestAnimationFrame에서 진행률에 의해 제어되며, 부드럽게 처리됩니다; 탐색을 병합합니다(Safari는 큐를 누적).- 100% 탐색 가능하도록 Blob으로 로드합니다; 짧은 GOP(매 몇 프레임마다 키프레임)로 인코딩해야 스크럽이 끊기지 않습니다.
- 절대 자동 재생 금지: 스크롤이 움직입니다.
챕터 및 오버레이
- 진행률 범위(10%, 35%, 60%, 85%...)로 정의된 비트(Beats), 페이드 인/아웃 및 리듬을 위한 교차 진입 방향.
- 각 챕터는 SEO를 위해 올바른 의미론적 순서로 DOM에 텍스트를 유지합니다.
- 수평 갤러리: 섹션 고정 + 진행률에 연결된
x; 카드는 중앙으로 스케일됩니다.
성능 및 검증
transform,opacity,filter만 애니메이션합니다.will-change는 필요한 경우에만 사용하고 완료 후 제거합니다.- 로컬 및 가능하면 실제 모바일 기기에서
requestAnimationFrame으로 프레임을 측정합니다. 중간급 노트북에서 60fps를 목표로 합니다. - Headless 캡처는 캔버스를 검은색으로 읽을 수 있습니다(버퍼 미보존): DOM으로 확인 (계산된 스타일, 요소,
window.debug에 노출된 상태) 및 축소 모드가 활성화된 상태에서 확인합니다. - 375/768/1280/1440에서 테스트합니다. 데스크톱 브라우저 크기 조정은 iOS의 메모리 충돌을 재현하지 않습니다. QA 보고서에 명시하십시오.
권장 데이터 구조
정렬된 '장면' 배열: 각 장면은 { type: 'frames' | 'overlay' | 'galeria', ... }입니다. 영화 재구성 = 목록 재정렬. 프레임은 패턴으로 참조됩니다(/seq/plano-01/0001.webp).