fix(B05): 종단 새 창 최대화 때 휠 이동 — 스크롤러가 놓인 창의 화면 틀 · 시계 사용(230-1)

메인 창 requestAnimationFrame 은 새 창 최대화로 메인 창이 가려지면 멈춤 · 휠은 막히고 이동 0.
휠 이동 · 끌기 뒤 클릭 삼킴 · 글자 선택 풀기 · 스크롤 뒤 다시 그리기 시계를 ownerDocument.defaultView 로.
새 시험 test_230_1_popout_wheel(가려진 메인 창 · 새 창 보통 · 최대화 · 원래 창).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
2026-10-10 17:42:52 +09:00
co-authored by Claude Opus 5.5
parent dc027cd738
commit dd483a8fbd
4 changed files with 240 additions and 7 deletions
+15 -5
View File
@@ -37,16 +37,25 @@ const BASE_FRAME_MS = 1000 / 60;
* 쓰면 한 번에 목표까지 튄다. */
const MAX_FRAME_MS = 64;
/** 스크롤러가 지금 놓인 창 — 종단을 새 창으로 빼면 그 창이다(230-1).
* 메인 창 전역 `requestAnimationFrame` 을 쓰면, 새 창을 최대화해 메인 창이 가려질 때
* 브라우저가 메인 창 프레임을 멈춰 휠 이동이 한 걸음도 안 나갔다. */
function viewOf(scroller: HTMLElement): Window {
return scroller.ownerDocument.defaultView ?? window;
}
export function attachWheelHorizontalScroll(scroller: HTMLElement): void {
let target = -1;
/** 우리가 마지막으로 써 넣은 값 — 남이 자리를 옮겼는지 가리는 데 쓴다. */
let applied = -1;
let frame = 0;
/** `frame` 을 받아 둔 창 — 걷을 때도 같은 창에서 걷는다. */
let frameView: Window = window;
/** 직전 프레임 시각 — 좁히는 양을 **흐른 시간**에 맞추는 데 쓴다. */
let lastFrameAt = 0;
function stop(): void {
if (frame) cancelAnimationFrame(frame);
if (frame) frameView.cancelAnimationFrame(frame);
frame = 0;
target = -1;
applied = -1;
@@ -72,7 +81,7 @@ export function attachWheelHorizontalScroll(scroller: HTMLElement): void {
const ratio = 1 - Math.pow(1 - EASE, elapsed / BASE_FRAME_MS);
scroller.scrollLeft = scroller.scrollLeft + rest * ratio;
applied = scroller.scrollLeft;
frame = requestAnimationFrame(step);
frame = frameView.requestAnimationFrame(step);
}
scroller.addEventListener(
@@ -89,7 +98,8 @@ export function attachWheelHorizontalScroll(scroller: HTMLElement): void {
target = next;
if (!frame) {
applied = scroller.scrollLeft;
frame = requestAnimationFrame(step);
frameView = viewOf(scroller);
frame = frameView.requestAnimationFrame(step);
}
event.preventDefault();
},
@@ -128,7 +138,7 @@ export function attachWheelHorizontalScroll(scroller: HTMLElement): void {
};
scroller.addEventListener("click", swallow, { capture: true, once: true });
// 클릭이 안 오는 경우(휠 버튼 등)를 대비해 다음 프레임에 걷어낸다.
requestAnimationFrame(() =>
viewOf(scroller).requestAnimationFrame(() =>
scroller.removeEventListener("click", swallow, { capture: true }),
);
}
@@ -164,7 +174,7 @@ export function attachWheelHorizontalScroll(scroller: HTMLElement): void {
// 끄는 동안 글자가 파랗게 잡히지 않게 한다. 포인터 잡기를 미룬 뒤로 브라우저가
// 기본 글자 선택을 시작했다(2026-09-06 사용자 지적). 이미 잡힌 것도 푼다.
scroller.style.userSelect = "none";
window.getSelection()?.removeAllRanges();
viewOf(scroller).getSelection()?.removeAllRanges();
}
scroller.scrollLeft = dragLeft - dx;
scroller.scrollTop = dragTop - dy;