30줄로 스크롤 제어 WebGL 히어로 만들기

발행: (2026년 5월 24일 AM 12:24 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

스크롤에 반응하는 히어로 섹션은 겉보기엔 복잡해 보이지만, 올바른 요소만 있으면 실제로는 그렇지 않습니다. 두 개의 이미지와 그 사이를 변형하는 셰이더, 그리고 스크롤 위치가 변형을 구동합니다. 바로 그것뿐입니다. 나머지는 모두 연결 작업에 불과합니다.
이 튜토리얼은 바로 그—스크롤 기반 WebGL 히어로—를 약 30줄의 JavaScript로 구현합니다. 순수 HTML이며, 프레임워크도 빌드 단계도 없습니다. CodePen이나 정적 HTML 파일에 넣기만 하면 바로 동작합니다.

세 가지 동작이 연결됩니다

  • 사용자가 히어로 섹션으로 스크롤을 내리면 이미지 A가 이미지 B로 변형됩니다.
  • 히어로가 뷰포트 중앙에 있을 때는 변형이 이미지 B에 머무릅니다.
  • 사용자가 히어로를 지나쳐 스크롤을 내리면 이미지 B가 다시 이미지 A로 변형됩니다.

이것이 표준적인 스크롤 기반 히어로 형태입니다. Apple 제품 페이지, Linear 마케팅, Vercel 사례 연구 등 많은 사이트가 이 패턴을 사용합니다. 핵심은 멈춤(hold) 부분인데, 이것이 없으면 전환이 너무 빨리 끝나 사용자가 최종 이미지를 거의 보지 못하게 됩니다.

두 개의 npm 패키지, 둘 다 MIT 라이선스이며 매우 작습니다.

pnpm add @vysmo/scroll @vysmo/transitions

압축하면 약 6KB 정도이며, 로고 SVG보다도 작습니다.

섹션, 캔버스, 전환 소스로 사용할 두 개의 <img> 요소, 그리고 페이지에 스크롤을 만들기 위한 위·아래 콘텐츠를 배치합니다:

Above the hero

스크롤을 내려 효과를 확인하세요.


Below the hero

스크롤을 계속 내려보세요.

<img> 요소는 display: none 으로 설정되어 있어 DOM에 렌더링되지 않지만, WebGL 텍스처 소스로 전달됩니다. 브라우저는 여전히 이미지를 디코딩하므로 필요합니다.

import { Runner, crossZoom } from "@vysmo/transitions";
import { createScrollTransition, scrollPlateau } from "@vysmo/scroll";

const section = document.querySelector("#hero")!;
const canvas = document.querySelector("#hero-canvas")!;
const from = document.querySelector("#img-a")!;
const to = document.querySelector("#img-b")!;

// 두 이미지가 디코딩될 때까지 기다린 뒤 텍스처로 사용합니다.
await Promise.all([from.decode(), to.decode()]);

// 캔버스 백킹 스토어를 CSS 크기에 맞춰 셰이더가 선명하게 렌더링되도록 합니다.
const dpr = Math.min(window.devicePixelRatio, 2);
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;

// 캔버스당 하나의 WebGL2 runner를 생성합니다. 컴파일된 프로그램과 FBO를 소유합니다.
const runner = new Runner({ canvas });

// 스크롤 진행도를 전환에 바인딩합니다.
createScrollTransition({
  section,
  runner,
  transition: crossZoom,
  from,
  to,
  // 섹션이 뷰포트의 30%~70% 구간에 있을 때 "to" 이미지를 유지합니다.
  ease: scrollPlateau(0.3, 0.7),
});

이게 전부입니다. 이제 눈에 띄지 않는 부분을 하나씩 살펴보겠습니다.

scrollPlateau가 하는 일

API 중 가장 강조하고 싶은 부분이며, 전체 패턴을 작동하게 하는 작은 아이디어입니다.
스크롤 진행도는 섹션이 뷰포트를 통과할 때 0 → 1 사이의 선형 값입니다. 이 값을 그대로 전환에 넣으면 섹션이 들어오는 순간부터 바로 변형이 시작되고, 나가는 순간에 바로 끝납니다. 즉, 사용자는 목적지 이미지를 아주 짧게만 보게 되죠. 만족스럽지 않죠.

scrollPlateau(0.3, 0.7) 은 그 선형 진행도를 욕조 곡선(bath‑tube curve) 으로 변형합니다:

  • 0.0 → 0.3 : 전환이 0 → 1 로 진행
  • 0.3 → 0.7 : 전환이 1에 머무름(“hold”)
  • 0.7 → 1.0 : 전환이 1 → 0 로 역재생

그 결과, 섹션이 들어올 때는 빠르게 변형이 일어나고, 중간 구간에서는 목적지 이미지가 충분히 머무르며, 나갈 때는 역방향으로 변형이 진행됩니다. 시각적으로 사용자는 “도착”했다는 느낌을 받게 됩니다.

플래토(plateau) 구간은 자유롭게 조정할 수 있습니다. 예를 들어 scrollPlateau(0.1, 0.9) 은 입·출구 전환을 매우 빠르게 만들고, scrollPlateau(0.4, 0.6) 은 더 느리고 의도적인 전환을 제공합니다.

전환 라인: crossZoom

전환 자체를 정의하는 부분입니다. 기본 제공 전환은 60가지이며, 식별자만 바꾸면 쉽게 교체할 수 있습니다.

import { pageCurl } from "@vysmo/transitions";
// ...
createScrollTransition({ section, runner, transition: pageCurl, from, to, ease });

이제 이미지 A가 페이지를 넘기듯 뒤로 물러나면서 이미지 B가 드러납니다. 동일한 30줄 코드이지만 전혀 다른 미학을 구현합니다.

히어로에 적합한 전환 몇 가지

  • crossZoom — 시네마틱 기본 전환; 이미지 A가 확대되며 B로 페이드
  • pageCurl — 잡지 페이지를 넘기는 듯한 편집적 느낌
  • paintBleed — 물감이 흘러내리듯 드러나는 효과, 제품 출시와 잘 어울림
  • warpZoom — 색채 왜곡 효과; 테크 감성에 최적
  • glassShatter — 이미지 A가 깨지며 B가 나타남; 고드라마, 남용 금지

전환을 바꾸려면 import와 transition 속성만 한 줄 바꾸면 됩니다. 전체 카탈로그와 실시간 미리보기는 공식 문서에 있습니다.

내부 동작 살펴보기

의심스러울 경우 실제로 어떤 코드가 실행되는지 확인해 보세요.

  1. createScrollTransition 은 섹션을 공유 IntersectionObserver와 단일 requestAnimationFrame 루프에 등록합니다(페이지 내 모든 Vysmo 스크롤 바인딩이 하나의 rAF를 공유하므로 성능에 유리).
  2. 섹션이 뷰포트와 교차하는 매 프레임마다
0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.