배경을 뷰포트에 고정하고, 게이트 씬을 파이프라인으로 옮긴다
"스크롤할 때 이미지가 고정되어 원경처럼 보이는 게 패럴랙스의 꽃"이라는
지적이 맞았다. 느리게 같이 움직이는 것으로는 그 인상이 안 나온다 —
속도만 다를 뿐 방향이 같기 때문이다.
고정 배경
background-attachment: fixed 는 iOS Safari 가 GPU 메모리 때문에
throttle 해서 스크롤 중 배경이 튄다. 쓰지 않았다.
섹션에 clip-path: inset(0) 을 걸어 position: fixed 의 컨테이닝 블록으로
만들었다. 배경이 화면에 멈춘 채 그 섹션 안에서만 보인다.
실측: 스크롤 1000px 구간에서 viewportTop 이 계속 0.
대가 둘을 받아들였다
조상에 transform/filter/mask 를 못 쓴다 — 페이드 마스크를 포기했다
prefers-reduced-motion 에서는 absolute 로 되돌린다
전부 고정하지는 않았다. 종이는 손에 닿는 거리라 원경으로 두면 거짓말이다
재료·설치·프리플라이트 fixed 원경
사례 drift 두 결과물이 흐르는 자리
조판 drift 종이는 가깝다
게이트 씬을 파이프라인으로
히어로에서는 의미를 판독기가 자막으로 설명해야 했다. 그림이 스스로
말하지 못한다는 뜻이다. 옆에 여섯 단계가 적힌 자리로 옮기니 자막이
필요 없어졌다 — 판을 통과하면 그 단계가 켜진다.
캔버스는 sticky, 진행도는 섹션에서 읽는다.
travel 을 섹션 높이로 잡았더니 캔버스가 사라진 뒤에도 진행이 남아
6단계 중 3단계에서 멈췄다. (섹션 높이 - 뷰포트 높이)로 고쳤다.
캔버스 위 텍스트 대비 14.2:1.
스킬
motion.md 패럴랙스의 원형은 고정 · clip-path 기법과 그 대가
sticky 캔버스의 진행도 계산
three.md 씬의 의미는 옆의 콘텐츠가 반응할 때 성립한다.
자막이 필요하면 배치를 의심해라
This commit is contained in:
parent
1ed3c95542
commit
af3a08397e
12 changed files with 281 additions and 56 deletions
|
|
@ -38,6 +38,12 @@ export interface HeroSceneOptions {
|
|||
* WebGL 은 셰이더만 만진다. 매 프레임 DOM 을 쓰면 그 경계가 무너진다.
|
||||
*/
|
||||
onGate?: (index: number, total: number) => void;
|
||||
/**
|
||||
* 스크롤 진행도를 이 요소로 잰다. 기본은 캔버스 자신.
|
||||
* 캔버스를 sticky/fixed 로 화면에 붙여두면 캔버스의 rect 는 변하지 않으므로
|
||||
* 진행도가 고정된다 — 그때는 실제로 지나가는 섹션을 넘겨야 한다.
|
||||
*/
|
||||
progressFrom?: HTMLElement;
|
||||
}
|
||||
|
||||
const NOISE = `
|
||||
|
|
@ -70,7 +76,7 @@ export function mountHeroScene(opts: HeroSceneOptions): void {
|
|||
io.observe(canvas);
|
||||
}
|
||||
|
||||
async function boot({ canvas, accentVar, glowVar, onGate }: HeroSceneOptions): Promise<void> {
|
||||
async function boot({ canvas, accentVar, glowVar, onGate, progressFrom }: HeroSceneOptions): Promise<void> {
|
||||
let THREE: typeof import("three");
|
||||
try {
|
||||
THREE = await import("three");
|
||||
|
|
@ -305,10 +311,15 @@ async function boot({ canvas, accentVar, glowVar, onGate }: HeroSceneOptions): P
|
|||
|
||||
// 스크롤 진행도. addEventListener("scroll") 을 쓰지 않는다(하드 게이트 10).
|
||||
// rect 는 프레임당 한 번만 읽는다. 두 번 읽으면 레이아웃을 두 번 강제한다.
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
// 액자가 화면 위로 92% 지나갔을 때 마지막 게이트를 통과하게 잡았다.
|
||||
// 그 뒤로는 액자가 보이지 않으므로 계속 진행시킬 이유가 없다.
|
||||
const travel = rect.height * 0.92;
|
||||
const rect = (progressFrom ?? canvas).getBoundingClientRect();
|
||||
// 진행 거리.
|
||||
// progressFrom 이 있으면 캔버스는 sticky 로 화면에 붙어 있고 섹션이 지나간다.
|
||||
// 이때 캔버스가 실제로 화면에 머무는 거리는 (섹션 높이 - 뷰포트 높이)다.
|
||||
// 섹션 높이를 그대로 쓰면 캔버스가 사라진 뒤에도 진행이 남아 마지막 게이트에
|
||||
// 영영 도달하지 못한다 — 실측에서 6단계 중 3단계에서 멈췄다.
|
||||
const travel = progressFrom
|
||||
? Math.max(rect.height - innerHeight, 1)
|
||||
: rect.height * 0.92;
|
||||
const progress = Math.min(Math.max(-rect.top / travel, 0), 1);
|
||||
|
||||
bgUniforms.uProgress.value = progress;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue