perf(editor): improve resize frame rate (#10967)
This PR improves panel toggling FPS by minimizing DOM and canvas updates during resizing. [Screen Recording 2025-03-18 at 11.57.45 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/lEGcysB4lFTEbCwZ8jMv/d36beeab-a2e4-4fd6-923a-705ce7fbcdf7.mov" />](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/d36beeab-a2e4-4fd6-923a-705ce7fbcdf7.mov) ### What changed? - Added a debounced resize handling mechanism using RxJS's `debounceTime` operator - Implemented a new resize strategy that preserves the top-left corner position during resizing - Added state tracking for resize operations with `_isResizing` flag and `_initialTopLeft` coordinates
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '@blocksuite/global/gfx';
|
} from '@blocksuite/global/gfx';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
import { Subject } from 'rxjs';
|
import { debounceTime, Subject } from 'rxjs';
|
||||||
|
|
||||||
import type { GfxViewportElement } from '.';
|
import type { GfxViewportElement } from '.';
|
||||||
|
|
||||||
@@ -54,6 +54,16 @@ export class Viewport {
|
|||||||
|
|
||||||
private _resizeObserver: ResizeObserver | null = null;
|
private _resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
private readonly _resizeSubject = new Subject<{
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
private _isResizing = false;
|
||||||
|
private _initialTopLeft: IVec | null = null;
|
||||||
|
|
||||||
protected _center: IPoint = { x: 0, y: 0 };
|
protected _center: IPoint = { x: 0, y: 0 };
|
||||||
|
|
||||||
protected _shell: HTMLElement | null = null;
|
protected _shell: HTMLElement | null = null;
|
||||||
@@ -110,6 +120,35 @@ export class Viewport {
|
|||||||
this._element = el;
|
this._element = el;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._setupResizeObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setupResizeObserver() {
|
||||||
|
this._resizeSubject
|
||||||
|
.pipe(debounceTime(200))
|
||||||
|
.subscribe(({ width, height, left, top }) => {
|
||||||
|
if (!this._shell || !this._initialTopLeft) return;
|
||||||
|
|
||||||
|
const [initialTopLeftX, initialTopLeftY] = this._initialTopLeft;
|
||||||
|
const newCenterX = initialTopLeftX + width / (2 * this.zoom);
|
||||||
|
const newCenterY = initialTopLeftY + height / (2 * this.zoom);
|
||||||
|
|
||||||
|
this.setCenter(newCenterX, newCenterY);
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._left = left;
|
||||||
|
this._top = top;
|
||||||
|
this._isResizing = false;
|
||||||
|
this._initialTopLeft = null;
|
||||||
|
|
||||||
|
this.sizeUpdated.next({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get boundingClientRect() {
|
get boundingClientRect() {
|
||||||
@@ -239,6 +278,8 @@ export class Viewport {
|
|||||||
this.sizeUpdated.complete();
|
this.sizeUpdated.complete();
|
||||||
this.viewportMoved.complete();
|
this.viewportMoved.complete();
|
||||||
this.viewportUpdated.complete();
|
this.viewportUpdated.complete();
|
||||||
|
this._resizeSubject.complete();
|
||||||
|
|
||||||
this.zooming$.value = false;
|
this.zooming$.value = false;
|
||||||
this.panning$.value = false;
|
this.panning$.value = false;
|
||||||
}
|
}
|
||||||
@@ -281,18 +322,23 @@ export class Viewport {
|
|||||||
|
|
||||||
onResize() {
|
onResize() {
|
||||||
if (!this._shell) return;
|
if (!this._shell) return;
|
||||||
const { centerX, centerY, zoom, width: oldWidth, height: oldHeight } = this;
|
|
||||||
|
if (!this._isResizing) {
|
||||||
|
this._isResizing = true;
|
||||||
|
this._initialTopLeft = this.toModelCoord(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
const { left, top, width, height } = this.boundingClientRect;
|
const { left, top, width, height } = this.boundingClientRect;
|
||||||
this._cachedOffsetWidth = this._shell.offsetWidth;
|
this._cachedOffsetWidth = this._shell.offsetWidth;
|
||||||
|
|
||||||
this.setRect(left, top, width, height);
|
this._left = left;
|
||||||
this.setCenter(
|
this._top = top;
|
||||||
centerX - (oldWidth - width) / zoom / 2,
|
this._resizeSubject.next({
|
||||||
centerY - (oldHeight - height) / zoom / 2
|
left,
|
||||||
);
|
top,
|
||||||
|
width,
|
||||||
this._width = width;
|
height,
|
||||||
this._height = height;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setCenter(centerX: number, centerY: number) {
|
setCenter(centerX: number, centerY: number) {
|
||||||
@@ -307,6 +353,12 @@ export class Viewport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRect(left: number, top: number, width: number, height: number) {
|
setRect(left: number, top: number, width: number, height: number) {
|
||||||
|
if (this._isResizing) {
|
||||||
|
this._left = left;
|
||||||
|
this._top = top;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._left = left;
|
this._left = left;
|
||||||
this._top = top;
|
this._top = top;
|
||||||
this.sizeUpdated.next({
|
this.sizeUpdated.next({
|
||||||
|
|||||||
Reference in New Issue
Block a user