mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
perf(editor): lazy DOM update with idle state in gfx viewport (#10624)
Currently, `GfxViewportElement` hides DOM blocks outside the viewport using `display: none` to optimize performance. However, this approach presents two issues: 1. Even when hidden, all top-level blocks still undergo frequent CSS transform updates during viewport panning and zooming. 2. Hidden blocks cannot access DOM layout information, preventing `TurboRenderer` from updating the complete canvas bitmap. To address this, this PR introduces a refactoring that divides all top-level edgeless blocks into two states: `idle` and `active`. The improvements are as follows: 1. Blocks outside the viewport are set to the `idle` state, meaning they no longer update their DOM during viewport panning or zooming. Only `active` blocks within the viewport are updated frame by frame. 2. For `idle` blocks, the hiding method switches from `display: none` to `visibility: hidden`, ensuring their layout information remains accessible to `TurboRenderer`. [Screen Recording 2025-03-07 at 3.23.56 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/4bac640b-f5b6-4b0b-904d-5899f96cf375.mov" />](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/4bac640b-f5b6-4b0b-904d-5899f96cf375.mov) While this minimizes DOM updates, it introduces a trade-off: `idle` blocks retain an outdated layout state. Since their positions are updated using a lazy update strategy, their layout state remains frozen at the moment they were last moved out of the viewport:  To resolve this, the PR serializes and stores the viewport field of the block at that moment on the `idle` block itself. This allows the correct layout, positioned in the model coordinate system, to be restored from the stored data.
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
getImageSelectionsCommand,
|
||||
getSelectedBlocksCommand,
|
||||
getSelectedModelsCommand,
|
||||
getSurfaceBlock,
|
||||
getTextSelectionCommand,
|
||||
ImageBlockModel,
|
||||
isCanvasElement,
|
||||
@@ -197,7 +198,7 @@ export const stopPropagation = (e: Event) => {
|
||||
|
||||
export function getSurfaceElementFromEditor(editor: EditorHost) {
|
||||
const { doc } = editor;
|
||||
const surfaceModel = doc.getBlockByFlavour('affine:surface')[0];
|
||||
const surfaceModel = getSurfaceBlock(doc);
|
||||
if (!surfaceModel) return null;
|
||||
|
||||
const surfaceId = surfaceModel.id;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
FontFamilyMap,
|
||||
FontStyle,
|
||||
FontWeightMap,
|
||||
getSurfaceBlock,
|
||||
PointStyle,
|
||||
StrokeStyle,
|
||||
TextAlign,
|
||||
@@ -29,7 +30,6 @@ import { menuTrigger, settingWrapper } from '../style.css';
|
||||
import { sortedFontWeightEntries, usePalettes } from '../utils';
|
||||
import { Point } from './point';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
import { getSurfaceBlock } from './utils';
|
||||
|
||||
enum ConnecterStyle {
|
||||
General = 'general',
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { LayoutType, MindmapStyle } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
getSurfaceBlock,
|
||||
LayoutType,
|
||||
MindmapStyle,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@@ -15,7 +19,6 @@ import { useCallback, useMemo } from 'react';
|
||||
import { DropdownMenu } from '../menu';
|
||||
import { menuTrigger, settingWrapper } from '../style.css';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
import { getSurfaceBlock } from './utils';
|
||||
|
||||
const MINDMAP_STYLES = [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MenuItem, MenuTrigger, Slider } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { DefaultTheme } from '@blocksuite/affine/blocks';
|
||||
import { DefaultTheme, getSurfaceBlock } from '@blocksuite/affine/blocks';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { useFramework, useLiveData } from '@toeverything/infra';
|
||||
import { isEqual } from 'lodash-es';
|
||||
@@ -13,7 +13,6 @@ import { menuTrigger } from '../style.css';
|
||||
import { usePalettes } from '../utils';
|
||||
import { Point } from './point';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
import { getSurfaceBlock } from './utils';
|
||||
|
||||
export const PenSettings = () => {
|
||||
const t = useI18n();
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
FontStyle,
|
||||
FontWeightMap,
|
||||
getShapeName,
|
||||
getSurfaceBlock,
|
||||
ShapeStyle,
|
||||
ShapeType,
|
||||
StrokeStyle,
|
||||
@@ -39,7 +40,6 @@ import { sortedFontWeightEntries, usePalettes } from '../utils';
|
||||
import type { DocName } from './docs';
|
||||
import { Point } from './point';
|
||||
import { EdgelessSnapshot } from './snapshot';
|
||||
import { getSurfaceBlock } from './utils';
|
||||
|
||||
enum ShapeTextFontSize {
|
||||
'16px' = '16',
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine/block-std/gfx';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine/blocks';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
|
||||
export function getSurfaceBlock(doc: Store) {
|
||||
const blocks = doc.getBlocksByFlavour('affine:surface');
|
||||
return blocks.length !== 0 ? (blocks[0].model as SurfaceBlockModel) : null;
|
||||
}
|
||||
|
||||
export function getFrameBlock(doc: Store) {
|
||||
const blocks = doc.getBlocksByFlavour('affine:frame');
|
||||
return blocks.length !== 0 ? (blocks[0].model as FrameBlockModel) : null;
|
||||
|
||||
Reference in New Issue
Block a user