Files
Aislo/A00_Common/design/design_profile_viewport.ts
T
eomsangdonandClaude Opus 5.5 e0ad372e70 refactor(B05·B06): 종단도 700줄 정리 — 오버레이 · 스크롤 듣개 새 파일로 떼어 641줄(213-3)
- design_profile_overlays.ts — 유토곡선 · 테이블 오버레이 · 유토 총괄 배지 만들기
- design_profile_viewport.ts — 가로 스크롤 다시 그림 · 휠 가로 스크롤 · 크기 바뀜 듣개(dispose 때 뗌)
- 동작 그대로 · test_215_1 줄 수 기준 = 700(규칙) · 새 두 파일도 검사

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
2026-10-10 15:53:42 +09:00

67 lines
3.3 KiB
TypeScript

/* =============================================================================
* design_profile_viewport.ts
* 종단 스크롤러의 가로 스크롤 · 휠 · 크기 바뀜 듣개(213-3 · 700줄 한계로 뗌).
* 다시 그리기 · 높이 동기화는 종단도가 넘긴 함수를 부른다.
* ========================================================================== */
import { attachWheelHorizontalScroll } from "../../B05_Profile/B05_Profile_UI_Profile_Wheel";
import { needsFullRedraw, type ElevationWindowResult } from "@util/common_util_chart_ywindow";
/** 그래프를 통째로 다시 만들 때까지 기다리는 시간(ms) — 눈금 갱신으로도 못 살릴 때만. */
const SCROLL_SETTLE_MS = 80;
export interface ProfileViewportDeps {
body: HTMLElement;
/** ▲▼ 계획고 편집으로 Y 축을 잡고 있는 중. */
editing: () => boolean;
/** 세로 창 갱신기 — 그릴 때마다 렌더러가 새로 준다(없으면 null). */
fitElevation: () => ElevationWindowResult | null;
draw: () => void;
/** 마지막으로 그린 본문 크기 — 같으면 크기 바뀜을 무시한다. */
lastSize: () => { width: number; height: number };
scheduleLightSync: () => void;
scheduleDraw: () => void;
}
/** 듣개를 붙이고 떼는 함수를 돌려준다. */
export function bindProfileViewport(deps: ProfileViewportDeps): () => void {
const { body } = deps;
/** 마지막으로 통째로 다시 만든 가로 위치 — 같은 자리면 다시 그리지 않는다. */
let settledScrollLeft = 0;
let scrollSettleTimer = 0;
// 세로 맞춤은 **그리기가 아니라 변환**이다(2026-09-04 사용자 확정) — 스크롤마다 겹 하나의
// 변환만 갈아 끼우므로 실시간으로 따라온다. 눈금 간격이 어긋나면 눈금층(요소 20개 안팎)만
// 새로 만든다. 그래프를 통째로 다시 만드는 34ms 짜리 재구성은 그래도 안 될 때만 남는다.
body.addEventListener("scroll", () => {
if (deps.editing()) return;
const fitted = deps.fitElevation();
window.clearTimeout(scrollSettleTimer);
if (!needsFullRedraw(fitted)) return;
scrollSettleTimer = window.setTimeout(() => {
if (deps.editing() || Math.abs(body.scrollLeft - settledScrollLeft) < 1) return;
settledScrollLeft = body.scrollLeft;
deps.draw();
}, SCROLL_SETTLE_MS);
});
// 종단면도는 가로로 매우 길다. 세로 휠을 가로 스크롤로 돌려 스크롤바를 잡지 않고도
// 노선을 훑을 수 있게 한다 (Shift+휠은 브라우저 기본 가로 스크롤이라 그대로 둔다).
attachWheelHorizontalScroll(body);
const resizeObserver = new ResizeObserver(() => {
const last = deps.lastSize();
if (
body.clientWidth <= 0 ||
body.clientHeight <= 0 ||
(Math.abs(body.clientWidth - last.width) < 1 && Math.abs(body.clientHeight - last.height) < 1)
)
return;
// 끌리는 동안엔 프레임당 경량 높이 동기화만 — 무거운 전체 재구성은 손을 뗀 뒤 한 번.
// (150ms 디바운스만 있던 예전 방식은 중간 프레임이 없어 툭툭 끊겼다. 2026-08-05 사용자 보고)
deps.scheduleLightSync();
deps.scheduleDraw();
});
resizeObserver.observe(body);
return () => resizeObserver.disconnect();
}