From 80f62a995b2ce612b4603ad2366fc8bf8bbd807c Mon Sep 17 00:00:00 2001
From: doodlewind <7312949+doodlewind@users.noreply.github.com>
Date: Wed, 19 Mar 2025 04:42:23 +0000
Subject: [PATCH] perf(editor): improve resize frame rate (#10967)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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 (uploaded via Graphite)
](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
---
.../framework/block-std/src/gfx/viewport.ts | 72 ++++++++++++++++---
1 file changed, 62 insertions(+), 10 deletions(-)
diff --git a/blocksuite/framework/block-std/src/gfx/viewport.ts b/blocksuite/framework/block-std/src/gfx/viewport.ts
index 59190e73c..d2746e99a 100644
--- a/blocksuite/framework/block-std/src/gfx/viewport.ts
+++ b/blocksuite/framework/block-std/src/gfx/viewport.ts
@@ -7,7 +7,7 @@ import {
} from '@blocksuite/global/gfx';
import { signal } from '@preact/signals-core';
import debounce from 'lodash-es/debounce';
-import { Subject } from 'rxjs';
+import { debounceTime, Subject } from 'rxjs';
import type { GfxViewportElement } from '.';
@@ -54,6 +54,16 @@ export class Viewport {
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 _shell: HTMLElement | null = null;
@@ -110,6 +120,35 @@ export class Viewport {
this._element = el;
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() {
@@ -239,6 +278,8 @@ export class Viewport {
this.sizeUpdated.complete();
this.viewportMoved.complete();
this.viewportUpdated.complete();
+ this._resizeSubject.complete();
+
this.zooming$.value = false;
this.panning$.value = false;
}
@@ -281,18 +322,23 @@ export class Viewport {
onResize() {
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;
this._cachedOffsetWidth = this._shell.offsetWidth;
- this.setRect(left, top, width, height);
- this.setCenter(
- centerX - (oldWidth - width) / zoom / 2,
- centerY - (oldHeight - height) / zoom / 2
- );
-
- this._width = width;
- this._height = height;
+ this._left = left;
+ this._top = top;
+ this._resizeSubject.next({
+ left,
+ top,
+ width,
+ height,
+ });
}
setCenter(centerX: number, centerY: number) {
@@ -307,6 +353,12 @@ export class Viewport {
}
setRect(left: number, top: number, width: number, height: number) {
+ if (this._isResizing) {
+ this._left = left;
+ this._top = top;
+ return;
+ }
+
this._left = left;
this._top = top;
this.sizeUpdated.next({