fix(site): 스파인 뒤 62dvh 여백을 없애고 게이트 진행도를 목록 기준으로 잰다
진행도를 섹션 높이로 재고 있어서, 여섯 게이트를 다 돌리려면 정거장 목록 뒤에 화면 절반이 넘는 빈 공간(558px)이 필요했다. 콘텐츠가 아니라 스크롤 연료였고 화면에서는 그냥 큰 빈칸으로 보였다. 진행도를 목록이 화면을 지나는 구간으로 바꿨다. start = 뷰포트 70% 지점에 목록 위쪽이 닿으면 0 end = 뷰포트 30% 지점을 목록 아래쪽이 빠져나가면 1 travel = 목록 높이 + (start - end) 뷰포트의 40% 가 구간으로 더해지므로, 목록 963px 이 1323px 구간이 되고 게이트당 220px 이다. 여백을 다 지우고도 과민 반응 기준(200px)을 넘긴다. motion.md 에 "스크롤 구간이 모자라면 여백이 아니라 진행도 공식을 고쳐라" 를 기록했다.
This commit is contained in:
parent
bd1daa7ec3
commit
213e7f5748
3 changed files with 50 additions and 17 deletions
|
|
@ -225,11 +225,10 @@ const { c } = Astro.props;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sticky 게이트가 끝까지 돌려면 스파인 뒤에 스크롤 구간이 필요하다.
|
/* 한때 여기에 padding-bottom: 62dvh 가 있었다. 게이트 진행도를 섹션 높이로
|
||||||
한동안 여기에 마무리 문장을 한 줄 놓아 그 자리를 채웠는데,
|
재고 있어서, 여섯 단계를 다 돌리려면 목록 뒤에 화면 절반이 넘는 빈 공간이
|
||||||
읽는 사람에게는 뜬금없는 문장이었다 — 여백이 필요하면 여백을 만든다.
|
필요했다. 콘텐츠가 아니라 스크롤 연료였다.
|
||||||
문장으로 여백을 대신하지 않는다. */
|
진행도를 목록이 화면을 지나는 구간으로 바꾸면서 그 여백이 필요 없어졌다. */
|
||||||
.spine { padding-bottom: 62dvh; }
|
|
||||||
|
|
||||||
@media (max-width: 780px) {
|
@media (max-width: 780px) {
|
||||||
.stop {
|
.stop {
|
||||||
|
|
@ -252,16 +251,19 @@ const { c } = Astro.props;
|
||||||
|
|
||||||
const canvas = document.getElementById("pipeline-canvas") as HTMLCanvasElement | null;
|
const canvas = document.getElementById("pipeline-canvas") as HTMLCanvasElement | null;
|
||||||
const section = document.getElementById("pipeline");
|
const section = document.getElementById("pipeline");
|
||||||
|
const spine = document.querySelector<HTMLElement>("#pipeline .spine");
|
||||||
const stops = [...document.querySelectorAll<HTMLElement>("#pipeline .stop")];
|
const stops = [...document.querySelectorAll<HTMLElement>("#pipeline .stop")];
|
||||||
|
|
||||||
if (canvas && section && stops.length) {
|
if (canvas && section && spine && stops.length) {
|
||||||
let last = -1;
|
let last = -1;
|
||||||
mountGateScene({
|
mountGateScene({
|
||||||
canvas,
|
canvas,
|
||||||
accentVar: "--accent",
|
accentVar: "--accent",
|
||||||
glowVar: "--glow-2",
|
glowVar: "--glow-2",
|
||||||
// 캔버스는 sticky 라 rect 가 변하지 않는다. 진행도는 섹션에서 읽는다.
|
// 캔버스는 sticky 라 rect 가 변하지 않는다.
|
||||||
progressFrom: section,
|
// 진행도는 섹션이 아니라 **정거장 목록**에서 읽는다 — 게이트가 도는 구간과
|
||||||
|
// 읽는 구간이 같아야 목록 뒤에 스크롤용 빈 공간을 두지 않아도 된다.
|
||||||
|
progressFrom: spine,
|
||||||
onGate(index) {
|
onGate(index) {
|
||||||
if (index === last) return;
|
if (index === last) return;
|
||||||
last = index;
|
last = index;
|
||||||
|
|
|
||||||
|
|
@ -319,14 +319,20 @@ async function boot({ canvas, accentVar, glowVar, onGate, progressFrom }: GateSc
|
||||||
// rect 는 프레임당 한 번만 읽는다. 두 번 읽으면 레이아웃을 두 번 강제한다.
|
// rect 는 프레임당 한 번만 읽는다. 두 번 읽으면 레이아웃을 두 번 강제한다.
|
||||||
const rect = (progressFrom ?? canvas).getBoundingClientRect();
|
const rect = (progressFrom ?? canvas).getBoundingClientRect();
|
||||||
// 진행 거리.
|
// 진행 거리.
|
||||||
// progressFrom 이 있으면 캔버스는 sticky 로 화면에 붙어 있고 섹션이 지나간다.
|
// 한때 (섹션 높이 - 뷰포트)로 쟀다. 그러면 진행 구간이 섹션 길이에 묶여서,
|
||||||
// 이때 캔버스가 실제로 화면에 머무는 거리는 (섹션 높이 - 뷰포트 높이)다.
|
// 게이트를 다 돌리려면 목록 뒤에 62dvh 짜리 빈 공간을 둬야 했다.
|
||||||
// 섹션 높이를 그대로 쓰면 캔버스가 사라진 뒤에도 진행이 남아 마지막 게이트에
|
// 화면 절반이 넘는 여백이 생기고, 그건 콘텐츠가 아니라 스크롤 연료였다.
|
||||||
// 영영 도달하지 못한다 — 실측에서 6단계 중 3단계에서 멈췄다.
|
//
|
||||||
|
// 대신 **읽는 대상이 화면을 지나는 동안**으로 맞춘다.
|
||||||
|
// 위 70% 지점에 닿으면 0, 아래 30% 지점을 빠져나가면 1.
|
||||||
|
// 목록이 눈앞에 있는 동안만 게이트가 돌아서 빈 공간이 필요 없다.
|
||||||
|
const start = innerHeight * 0.7;
|
||||||
|
const end = innerHeight * 0.3;
|
||||||
const travel = progressFrom
|
const travel = progressFrom
|
||||||
? Math.max(rect.height - innerHeight, 1)
|
? rect.height + (start - end)
|
||||||
: rect.height * 0.92;
|
: rect.height * 0.92;
|
||||||
const progress = Math.min(Math.max(-rect.top / travel, 0), 1);
|
const offset = progressFrom ? start - rect.top : -rect.top;
|
||||||
|
const progress = Math.min(Math.max(offset / travel, 0), 1);
|
||||||
|
|
||||||
bgUniforms.uProgress.value = progress;
|
bgUniforms.uProgress.value = progress;
|
||||||
bgUniforms.uTime.value = t;
|
bgUniforms.uTime.value = t;
|
||||||
|
|
|
||||||
|
|
@ -654,12 +654,37 @@ sticky 캔버스의 진행도는 `섹션 높이 − 뷰포트` 다. 그래서 "
|
||||||
.after { padding-bottom: 62dvh; } /* 스크롤 구간 — 계산으로 정한다 */
|
.after { padding-bottom: 62dvh; } /* 스크롤 구간 — 계산으로 정한다 */
|
||||||
```
|
```
|
||||||
|
|
||||||
여백을 **마지막 항목 뒤**에 몰면, 목록은 촘촘하게 읽히고 스크롤 구간은 확보된다.
|
|
||||||
목록이 끝난 뒤의 공백은 빈칸이 아니라 여운으로 읽힌다 — 스크롤텔링의 기본 문법이다.
|
|
||||||
|
|
||||||
판정 기준: **게이트 하나당 200px 이상**. 휠 한 번이 100~150px 이므로
|
판정 기준: **게이트 하나당 200px 이상**. 휠 한 번이 100~150px 이므로
|
||||||
그 아래면 "조금만 스크롤해도 다 지나가는" 과민한 반응이 된다.
|
그 아래면 "조금만 스크롤해도 다 지나가는" 과민한 반응이 된다.
|
||||||
|
|
||||||
|
### 스크롤 구간이 모자라면 여백이 아니라 **진행도 공식**을 고쳐라
|
||||||
|
|
||||||
|
위 문제를 처음에는 목록 뒤에 여백을 넣어 풀었다. `padding-bottom: 62dvh`.
|
||||||
|
계산은 맞았고 게이트는 끝까지 돌았는데, 화면 절반이 넘는 빈 공간이 남았다.
|
||||||
|
사용자가 본 것은 그 빈칸이었다 — "어마어마하게 큰 이상한 게 있는데요?"
|
||||||
|
|
||||||
|
**그 여백은 콘텐츠가 아니라 스크롤 연료였다.** 원인은 진행도를 섹션 높이로
|
||||||
|
잰 것이다. 그러면 진행 구간이 섹션 길이에 묶이고, 구간을 늘리는 유일한 방법이
|
||||||
|
섹션을 늘리는 것뿐이다.
|
||||||
|
|
||||||
|
진행도를 **읽는 대상이 화면을 지나는 동안**으로 바꾸면 여백이 필요 없어진다.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 섹션이 아니라 목록에서 읽는다. 게이트가 도는 구간과 읽는 구간이 같아진다.
|
||||||
|
const rect = list.getBoundingClientRect();
|
||||||
|
const start = innerHeight * 0.7; // 목록 위쪽이 여기 오면 진행 0
|
||||||
|
const end = innerHeight * 0.3; // 목록 아래쪽이 여기를 빠져나가면 진행 1
|
||||||
|
const travel = rect.height + (start - end);
|
||||||
|
const progress = clamp01((start - rect.top) / travel);
|
||||||
|
```
|
||||||
|
|
||||||
|
`(start - end)` 만큼이 공짜로 얻는 구간이다. 뷰포트의 40% 를 쓰면
|
||||||
|
목록 963px 짜리가 1323px 구간이 되고, 게이트 6 개면 하나당 220px 이다.
|
||||||
|
**여백 558px 을 지우고도 기준을 넘겼다.**
|
||||||
|
|
||||||
|
> 확인할 것: sticky 요소가 `progress === 1` 에 닿기 전에 사라지지 않아야 한다.
|
||||||
|
> 목록 아래쪽이 화면 30% 에 있을 때 섹션 하단이 아직 뷰포트 안이면 안전하다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. JS 스택 판단 — 언제 끌어오고 언제 안 끌어오나
|
## 4. JS 스택 판단 — 언제 끌어오고 언제 안 끌어오나
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue