perf(layout): track panel resizing at a stepped cadence

This commit is contained in:
dengjie zhang authored and GitHub committed 2026-09-18 01:48:27 +08:00
1 parent a78fc48a7b
commit 8eba12ea2f
17 files changed
+705 -34

No files matched your search

+20 -1
View File
@@ -1,5 +1,6 @@
import { ref, type Ref } from "vue";
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
import { beginPanelResize, endPanelResize } from "@/lib/app/panelResizeState";
const PANEL_MIN_WIDTH = 240;
const DEFAULT_PANEL_MAX_WIDTH = 800;
@@ -36,6 +37,7 @@ export function usePanelResize() {
resizeHandle?.setPointerCapture(e.pointerId);
const resizeOverlay = document.createElement("div");
resizeOverlay.setAttribute("aria-hidden", "true");
beginPanelResize();
Object.assign(resizeOverlay.style, {
position: "fixed",
inset: "0",
@@ -53,19 +55,36 @@ export function usePanelResize() {
widthRef.value = startWidth;
const panelElement = resizeHandle?.parentElement;
let currentWidth = startWidth;
let rafId: number | null = null;
const onPointerMove = (ev: PointerEvent) => {
const delta = ev.clientX - startX;
currentWidth = Math.max(PANEL_MIN_WIDTH, Math.min(upperBound, startWidth + (direction === "right" ? delta : -delta)));
panelElement?.style.setProperty("width", `${currentWidth}px`);
// happy-dom unit tests assert styles synchronously (no frame advance),
// so apply immediately under the test mode instead of via rAF.
if (import.meta.env.MODE === "test") {
panelElement?.style.setProperty("width", `${currentWidth}px`);
return;
}
if (rafId !== null) return;
rafId = requestAnimationFrame(() => {
panelElement?.style.setProperty("width", `${currentWidth}px`);
rafId = null;
});
};
const finishResize = () => {
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
panelElement?.style.setProperty("width", `${currentWidth}px`);
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", finishResize);
document.removeEventListener("pointercancel", finishResize);
window.removeEventListener("blur", finishResize);
if (resizeHandle?.hasPointerCapture(e.pointerId)) resizeHandle.releasePointerCapture(e.pointerId);
endPanelResize();
resizeOverlay.remove();
widthRef.value = currentWidth;
safeLocalStorageSet(storageKey, String(widthRef.value));
@@ -1,4 +1,5 @@
import { onBeforeUnmount, ref, watch, type Ref } from "vue";
import { deferUntilPanelResizeEnd } from "@/lib/app/panelResizeState";
export function useTabScroll(tabsContainerRef: Ref<HTMLElement | null>) {
const hasTabOverflow = ref(false);
@@ -20,6 +21,10 @@ export function useTabScroll(tabsContainerRef: Ref<HTMLElement | null>) {
scrollThumbWidthPercent.value = 100;
return;
}
// Skip DOM geometry reads while a divider drag is in flight; they force a
// synchronous relayout of the whole document on every frame. Re-measure
// once when the drag finishes.
if (deferUntilPanelResizeEnd(updateScrollButtons)) return;
const maxScrollLeft = Math.max(0, el.scrollWidth - el.clientWidth);
hasTabOverflow.value = maxScrollLeft > 1;
const rawThumbWidth = el.scrollWidth > 0 ? (el.clientWidth / el.scrollWidth) * 100 : 100;
@@ -1,4 +1,5 @@
import { nextTick, onUnmounted, ref, watch, type Ref, type WatchSource } from "vue";
import { deferUntilPanelResizeEnd } from "@/lib/app/panelResizeState";
import { resolveNextEditorToolbarTier, type EditorToolbarTier } from "@/lib/tabs/editorToolbarLayout";
/**
@@ -33,6 +34,10 @@ export function useToolbarOverflow(rootRef: Ref<HTMLElement | null>, revalidateS
if (!element) {
return;
}
// clientWidth/scrollWidth reads force a document-wide synchronous relayout
// on every resize notification; skip them during divider drags and take a
// single measurement when the drag ends.
if (deferUntilPanelResizeEnd(measure)) return;
const next = resolveNextEditorToolbarTier({
tier: tier.value,
availableWidth: element.clientWidth,