그리드 레이아웃 리사이즈/줌 미반영 버그 수정 + 스크롤 성능 개선
증상: 창을 최대화하거나 썸네일 줌을 바꿔도 그리드가 옛 폭에 갇혀 오른쪽이 비고 느리게 느껴짐. 근본 원인: <For>가 인덱스로 셀 DOM을 재사용하는데 셀의 x/y/w/h를 item 함수 본문에서 1회만 계산해, layout 변경(리사이즈/줌) 시 재사용된 셀 위치가 갱신되지 않았음(뷰 변경=인덱스 전면 교체 때만 반영됐음). 수정: - 셀 위치/크기를 반응형(box())으로 읽어 layout 변경 시 재배치 - 레이아웃 계산을 Web Worker→메인 스레드 동기 계산으로 전환 (프로덕션 Tauri 빌드에서 module worker가 첫 메시지 후 무응답) - 리사이즈/줌을 폴링(clientWidth+gridRowHeight 직접 읽기)+Ctrl휠 직접 recompute로 구동, 스냅샷 변경은 effect로. 이벤트 누락에 견고. - 가상 그리드 썸네일 lazy→eager 로딩으로 스크롤 중 즉시 표시 결과: 최대화 시 폭 99% 채움, 줌 재배치 정상, 스크롤 ~34→~57fps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// Justified 레이아웃 — 그리디 행 채우기. 메인 스레드 동기 계산.
|
||||
// (프로덕션 Tauri 빌드에서 module worker가 첫 메시지 후 응답하지 않는 이슈를 피하기 위해
|
||||
// 워커 대신 동기 계산을 쓴다. 재계산은 리사이즈/줌/데이터 변경 시에만 발생하며
|
||||
// 5만 항목도 수십 ms 내에 끝나 스크롤 성능에 영향 없다.)
|
||||
|
||||
export interface LayoutResult {
|
||||
/** n*4: [x, y, w, h] */
|
||||
boxes: Float32Array;
|
||||
/** 행별 상단 y (마지막 요소 = 전체 높이) */
|
||||
rowTops: Float32Array;
|
||||
/** 행별 첫 항목 인덱스 */
|
||||
rowFirst: Uint32Array;
|
||||
totalHeight: number;
|
||||
}
|
||||
|
||||
export function computeLayout(
|
||||
ratios: Float32Array,
|
||||
containerWidth: number,
|
||||
targetRowHeight: number,
|
||||
gap: number,
|
||||
): LayoutResult {
|
||||
const n = ratios.length;
|
||||
const boxes = new Float32Array(n * 4);
|
||||
const rowTopsArr: number[] = [];
|
||||
const rowFirstArr: number[] = [];
|
||||
|
||||
const clamp = (r: number) => Math.min(4, Math.max(0.25, r || 1));
|
||||
|
||||
let y = 0;
|
||||
let rowStart = 0;
|
||||
let ratioSum = 0;
|
||||
|
||||
const flushRow = (end: number, justify: boolean) => {
|
||||
const count = end - rowStart;
|
||||
if (count <= 0) return;
|
||||
const gaps = gap * (count - 1);
|
||||
let h = targetRowHeight;
|
||||
if (justify) {
|
||||
h = (containerWidth - gaps) / ratioSum;
|
||||
} else {
|
||||
const natural = ratioSum * targetRowHeight + gaps;
|
||||
if (natural > containerWidth) h = (containerWidth - gaps) / ratioSum;
|
||||
}
|
||||
let x = 0;
|
||||
for (let i = rowStart; i < end; i++) {
|
||||
const w = clamp(ratios[i]) * h;
|
||||
boxes[i * 4] = x;
|
||||
boxes[i * 4 + 1] = y;
|
||||
boxes[i * 4 + 2] = w;
|
||||
boxes[i * 4 + 3] = h;
|
||||
x += w + gap;
|
||||
}
|
||||
rowTopsArr.push(y);
|
||||
rowFirstArr.push(rowStart);
|
||||
y += h + gap;
|
||||
rowStart = end;
|
||||
ratioSum = 0;
|
||||
};
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
ratioSum += clamp(ratios[i]);
|
||||
const width = ratioSum * targetRowHeight + gap * (i - rowStart);
|
||||
if (width >= containerWidth) {
|
||||
flushRow(i + 1, true);
|
||||
}
|
||||
}
|
||||
flushRow(n, false);
|
||||
|
||||
const totalHeight = y > 0 ? y - gap : 0;
|
||||
rowTopsArr.push(totalHeight); // 마지막 = 전체 높이 (이진탐색 경계)
|
||||
|
||||
return {
|
||||
boxes,
|
||||
rowTops: new Float32Array(rowTopsArr),
|
||||
rowFirst: new Uint32Array(rowFirstArr),
|
||||
totalHeight,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user