Scroll Zoom Reveal
드롭인 방식의 의존성 없는 스크롤 효과:
- 고정된 전체 화면 씬: 뷰포트를 가득 채운 어두워진 씬 위에 헤드라인이 배치됩니다.
- 돌리 + 후진: 씬이 약간 안정화된 후, '카메라'가 뒤로 물러납니다. 씬이 사실은 노트북 디스플레이(베젤과 베이스가 보임) 위에 있음이 드러납니다.
- 앱 리빌: 에디터 패널(툴바, 아웃라이너, 속성, 타임라인)이 슬라이드 인하고 씬이 뷰포트 패널 안으로 축소됩니다.
- 하단에 텍스트 페이드 인(
data-zr-fade)과 슬라이딩 필 토글이 있는 탭 전환 갤러리가 나타납니다.
모든 바로 사용 가능한 파일은 이 파일과 같은 디렉토리의 assets/ 폴더에 있습니다:
| 파일 | 용도 |
|---|---|
assets/zoom-reveal.css | 모든 스타일 (.zr의 테마 토큰) |
assets/zoom-reveal.js | 컴포넌트 + 페이드 + 갤러리. window.ZoomReveal을 노출하며 [data-zoom-reveal] / [data-zr-gallery] / [data-zr-fade]를 자동 초기화합니다. |
assets/oled-field.js | 선택 사항. 커서에 반응하는 OLED 서브픽셀 필드(window.OledField, [data-zr-oled] 자동 초기화). 스타일은 zoom-reveal.css에 포함되어 있습니다. |
assets/placeholder-scenes.js | 선택 사항. 실제 이미지가 없을 때 캔버스에 절차적으로 원본 플레이스홀더 아트를 그립니다(ZRScenes.drawCity, ZRScenes.drawCanyon). |
assets/demo.html | 완전한 작동 페이지. 이 마크업에서 시작하세요. |
사이트에서 사용하는 방법
- assets 폴더를 프로젝트로 복사합니다 (예:
public/zoom-reveal/또는src/vendor/zoom-reveal/). 아직 실제 아트워크가 없다면placeholder-scenes.js만 복사하세요. assets/demo.html을 읽고<section class="zr" data-zoom-reveal>…</section>블록(그리고 원한다면 갤러리 블록)을 복사합니다. 클래스 구조를 정확히 유지하세요:
section.zr[data-zoom-reveal] ← 스크롤 길이가 여기 있습니다 (--zr-length)
.zr-sticky ← 고정된 100vh 스테이지
.zr-glow (선택 사항, 주변광)
.zr-device ← JS에 의해 스케일됨
.zr-lid > .zr-display
.zr-viewport > .zr-media > img | video | canvas ← 여러분의 씬
.zr-chrome > .zr-panel.zr-toolbar / .zr-left / .zr-right / .zr-bottom (선택 사항, 앱 UI)
.zr-base (노트북 베이스, 모니터/태블릿이라면 삭제)
.zr-scrim + header.zr-intro ← 씬 위의 헤드라인, 먼저 페이드 아웃
.zr-caption ← 작은 캡션, 끝에 페이드 인
<head>에zoom-reveal.css를 로드하고,<body>끝에zoom-reveal.js를 로드합니다.- 미디어 교체: 데모 캔버스를 실제 씬으로 교체하세요:
- 이미지:
<img src="scene.avif" alt="" decoding="async">. 최소 뷰포트 크기 × DPR(2560–3200px 너비)로 내보냅니다. 효과는 이미지가 화면 전체를 채우면서 시작됩니다. - 스크롤 연동 비디오:
<video data-zr-scrub muted playsinline preload="auto" src="scene.mp4"></video>. JS가 스크롤 진행률을currentTime에 매핑합니다. 부드러운 탐색을 위해 모든 프레임을 키프레임으로 인코딩하세요:ffmpeg -i in.mov -c:v libx264 -g 1 -crf 22 -an -movflags +faststart scene.mp4.
- 이미지:
- 모든 텍스트(아이브로우, 헤드라인, 캡션, 패널 레이블, 파일 이름, 갤러리 레이블)를 사이트에 맞게 다시 작성하세요. 데모의 가상 "Orbit / Forge" 텍스트를 절대 그대로 배포하지 마세요.
.zr의 토큰으로 테마를 지정하세요:--zr-bg,--zr-fg,--zr-muted,--zr-accent,--zr-length(스크롤 거리, 기본값360vh).
옵션
속성에 JSON을 전달하거나 (예: data-zoom-reveal='{"maxWidth":1000,"viewport":{"top":0,"right":0,"bottom":0,"left":0}}') JS로 생성할 수 있습니다: new ZoomReveal(el, options).
| 옵션 | 기본값 | 의미 |
|---|---|---|
maxWidth | 1180 | 최종 디스플레이 최대 너비 (px) |
widthRatio / heightRatio | 0.82 / 0.64 | 뷰포트 대비 최종 디스플레이 크기 |
aspect | 0.625 | 디스플레이 높이 ÷ 너비 (16:10). 휴대폰 ≈ 2.16, 울트라와이드 ≈ 0.42 |
overscan | 1.04 | 디스플레이가 시작되는 뷰포트 너머 거리 (베젤 숨김) |
dolly | 1.14 | 미디어의 시작 내부 줌; 1로 안정화됨 |
lift | 0.035 | 끝에서 기기가 올라가는 정도 |
timeline | JS 참조 | intro, dolly, zoom, chrome, caption에 대한 [start,end] 진행률 윈도우 |
viewport | {top:6.5,right:21,bottom:22,left:15} | 앱 UI 내 씬 패널의 최종 여백 (% of display). 앱 UI 없이 순수 "씬 → 디바이스 화면" 풀백을 원하면 모두 0으로 설정하고 .zr-chrome을 삭제하세요 |
onProgress | — | fn(p) 콜백, 진행률 0..1 |
이벤트: 섹션은 {width, height, finalWidth}와 함께 zr:resize를 디스패치합니다. 이는 미디어가 배치되는 픽셀 크기입니다. 캔버스 크기 조정이나 이미지 소스 선택에 사용하세요.
React / Next.js
CSS를 전역적으로 가져오세요. 마크업에서 data-zoom-reveal을 제거하고 컴포넌트 라이프사이클에 바인딩되도록 수동으로 생성하세요 (Next에서는 'use client'):
useEffect(() => {
import('./zoom-reveal/zoom-reveal.js').then(() => { // window.ZoomReveal 등록
const zr = new window.ZoomReveal(ref.current, { maxWidth: 1100 });
const gal = galRef.current && new window.ZoomReveal.Gallery(galRef.current);
window.ZoomReveal.initFades();
cleanup = () => zr.destroy();
});
let cleanup = () => {};
return () => cleanup();
}, []);
class → className, style="--v:78%" → style={{'--v':'78%'}}로 변환하세요.
OLED 픽셀 필드 (커서 호버 효과)
희미하게 빛나는 작은 RGB 서브픽셀 막대로 구성된 전체 화면 배경입니다. 커서를 움직이면 작은 유체 시뮬레이션이 활성화됩니다. 경로를 따라 픽셀이 밝아지고, 커지며, 보라색 → 시안 → 라임 → 따뜻한 흰색으로 색상이 변합니다. 그 후 빛이 커서 방향으로 표류하고 퍼져나가며 사라집니다. 클릭 또는 탭은 파문(ripple)을 보냅니다. 커서가 유휴 상태일 때(그리고 터치 스크린에서는) 느린 "유령 커서"가 효과를 유지합니다. 렌더링은 WebGL1 프래그먼트 셰이더로, 시뮬레이션은 작은 CPU 그리드이며, 섹션이 화면에 보이는 동안에만 실행됩니다. WebGL이 없으면 CSS 스포트라이트로 폴백합니다.
스펙/통계 블록, 히어로, 또는 CTA 밴드 뒤에 사용하세요. 어떤 요소든 작동합니다: class="zr-oled" data-zr-oled를 추가하고, 내부에 콘텐츠를 넣은 후 zoom-reveal.css + oled-field.js를 로드하세요. 컴포넌트가 캔버스를 직접 삽입합니다. assets/demo.html의 "OLED PIXEL FIELD" 블록을 참고하세요.
<section class="zr-oled" data-zr-oled='{"cell":7}'>
<div class="your-content">…통계 / 헤드라인…</div>
</section>
<script src="oled-field.js"></script>
| 옵션 | 기본값 | 의미 |
|---|---|---|
cell | 6 | 픽셀당 CSS px (서브픽셀 막대 3개). 5–9가 적당하며, 모바일에서는 성능을 위해 더 큰 값을 사용하세요. |
radius | 2.4 | 셀 단위 브러시 반경 |
strength | 0.55 | 커서가 추가하는 빛의 양. 기본값은 의도적으로 미묘함; 1은 대담함 |
decay | 0.94 | 프레임당 페이드; 높을수록 더 긴 잔상 (0.92 짧음 … 0.97 긺) |
flow | 0.55 | 빛이 커서 방향을 따라 표류하는 정도 |
diffusion | 0.35 | 빛이 퍼지는 속도 |
base | 0.09 | 불이 켜지지 않은 그리드의 밝기 |
palette | ['#5a3cff','#1fd6ff','#b6ff4a','#fff4d8'] | 4가지 색상, 낮은 에너지 → 높은 에너지. 브랜드에 맞게 조정하세요. 예: 따뜻한 ['#ff3c5a','#ff9a1f','#ffe14a','#fff8e8'] |
ambient / ripples | true | 유휴 상태일 때 유령 커서 / 클릭 파문 |
더 대담한 효과(쇼케이스 효과에 가깝게)를 원하면 {"radius":3.2,"strength":1,"decay":0.962,"flow":0.9}를 사용하세요. 텍스트가 많은 섹션 뒤에서는 미묘한 기본값을 유지하세요.
API: const f = new OledField(el, opts), f.pulse(clientX, clientY) (코드에서 파문 트리거), f.destroy() (React 정리에서 호출). 필드 위의 텍스트에는 부드러운 어두운 text-shadow를 주어 밝은 잔상 위에서도 읽기 쉽게 만드세요.
주의사항 (완료 전에 확인하세요)
- 고정(Sticky)이 깨짐:
.zr의 상위 요소 중overflow: hidden/auto/clip또는transform이 있으면 sticky가 깨집니다. 가로 오버플로를 숨겨야 한다면body에overflow-x: clip을 사용하세요. .zr을 페이지를 변환하는 부드러운 스크롤 라이브러리(Locomotive v4 등)로 감싸지 마세요. Lenis는 괜찮습니다.- 디바이스는 크게 배치되고 축소되므로 미디어가 선명하게 유지됩니다.
.zr-device안에 고정 px 크기를 넣지 마세요:em을 사용하세요 (1em = 디스플레이 너비의 1%). - 모션 감소가 처리됩니다: 섹션이 고정 해제되고 최종 상태를 표시하며, 갤러리는 자동 재생을 멈추고, OLED 필드는 커서에 계속 반응하지만 유령 커서와 파문은 제거됩니다.
- 백그라운드 탭에서 애니메이션이 일시 중지됩니다(requestAnimationFrame 중지). 브라우저 창에서 OLED 필드를 테스트할 때는 페이지가 보이는 상태로 유지하세요. 그렇지 않으면 멈춘 것처럼 보입니다.
- 너비가 약 500px 이하에서는 노트북이 작아집니다. 모바일에서는
{"widthRatio":0.92}또는 휴대폰 프레임에 맞는aspect를 고려하세요. - 데스크톱 및 모바일 너비에서 브라우저 상단에서 하단까지 천천히 스크롤하여 테스트하세요. 헤드라인이 풀백 시작 전에 페이드 아웃되는지, 시작 시 베젤이 보이지 않는지, 캡션이 베이스와 겹치지 않는지 확인하세요.
- 원본 또는 라이선스가 있는 아트워크와 텍스트를 사용하세요. 이것은 모션 기법을 재현하는 것입니다. 다른 브랜드의 이미지, 제품 렌더링 또는 텍스트를 복사하지 마세요.
