Files
Aislo/A00_Common/design/design_profile_viewport.ts
T
eomsangdonandClaude Opus 5.5 887c89bbf8 feat(b05,b06): 종단 그래프 — 다른 영역에서 고른 측점이 화면 밖이면 가운데로 이동 (273-1)
- 칠한 결과(고른 측점선 · 없으면 고른 구조물 알약)를 보고 보이는 칸 밖일 때만 가운데로 옮김(createSelectionReveal · design_profile_viewport)
- 같은 고름을 다시 칠하면 안 밂(사용자가 옮겨 본 자리 지킴) · 그래프 안 누름은 보이는 자리라 안 움직임 · 고른 뒤 다시 그리기가 끊지 않게 바로 옮김
- 요구 168 원문 기록 · 시험 test_273_1 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
2026-10-11 20:20:24 +09:00

101 lines
5.0 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";
import { viewOf } from "@util/common_util_view_window";
/** 그래프를 통째로 다시 만들 때까지 기다리는 시간(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;
/** 시계를 건 창 — 새 창으로 빼면 그 창 시계를 쓴다(가려진 메인 창 시계는 늦게 돎 · 230-1). */
let timerView: Window = window;
// 세로 맞춤은 **그리기가 아니라 변환**이다(2026-09-04 사용자 확정) — 스크롤마다 겹 하나의
// 변환만 갈아 끼우므로 실시간으로 따라온다. 눈금 간격이 어긋나면 눈금층(요소 20개 안팎)만
// 새로 만든다. 그래프를 통째로 다시 만드는 34ms 짜리 재구성은 그래도 안 될 때만 남는다.
body.addEventListener("scroll", () => {
if (deps.editing()) return;
const fitted = deps.fitElevation();
timerView.clearTimeout(scrollSettleTimer);
if (!needsFullRedraw(fitted)) return;
timerView = viewOf(body);
scrollSettleTimer = timerView.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();
}
/** 고른 것이 보이는 칸 가장자리에서 이만큼(폭 비율) 안쪽이면 그대로 둔다. */
const REVEAL_EDGE = 0.1;
/**
* 고른 측점선(없으면 고른 구조물 알약)이 종단도 보이는 칸 밖이면 가운데로 민다 — 다른 영역(횡단 카드 ·
* 3D · 배수유역도 · 좌측 목록)에서 고른 자리도 종단에서 바로 보이게. 칠한 결과(강조된 선 · 알약)를
* 보고 옮긴다 · 같은 고름을 다시 칠할 때는 안 밂(사용자가 옮겨 본 자리를 지킴) · 그래프 안에서
* 누른 것은 이미 보이는 자리라 안 움직임. `key` = 고름 열쇠(풀면 null).
*/
export function createSelectionReveal(body: HTMLElement): (key: string | null) => void {
let shown: string | null = null;
return (key) => {
if (key === null || key === shown) {
shown = key;
return;
}
const mark =
body.querySelector(".b06-chart__station--selected") ??
body.querySelector(".b05-structure__mark.is-selected");
if (!mark || body.clientWidth <= 0) return; // 아직 안 그려짐 — 다음 칠하기에서
shown = key;
const box = mark.getBoundingClientRect();
const view = body.getBoundingClientRect();
const at = box.left + box.width / 2 - view.left;
if (at >= view.width * REVEAL_EDGE && at <= view.width * (1 - REVEAL_EDGE)) return;
// 바로 옮김 — 고른 뒤 잇따르는 다시 그리기가 부드러운 이동을 끊음(다시 그리기는 지금 자리를 지킴)
body.scrollLeft += at - view.width / 2;
};
}