Skip to content

fix(core): prevent unnecessary node re-renders during canvas zoom#2398

Open
cyphercodes wants to merge 2 commits intodidi:masterfrom
cyphercodes:fix-zoom-rerender
Open

fix(core): prevent unnecessary node re-renders during canvas zoom#2398
cyphercodes wants to merge 2 commits intodidi:masterfrom
cyphercodes:fix-zoom-rerender

Conversation

@cyphercodes
Copy link
Copy Markdown

Description

Fixes #2396

When zooming the canvas, all nodes were unnecessarily re-rendering due to SCALE_X being destructured from the observable transformModel in the render() method. This caused mobx to track SCALE_X as a reactive dependency, triggering re-renders for every node whenever the zoom level changed.

Changes

  • Changed from destructuring SCALE_X in render() to accessing it directly via transformModel.SCALE_X when needed
  • This prevents mobx from establishing a reactive dependency on SCALE_X at the component level
  • The drag step is still correctly updated with the current scale, but without causing full re-renders

Impact

  • Significantly improved performance when zooming on diagrams with many nodes
  • Smoother zoom interactions, especially on complex flowcharts
  • No functional changes to the drag behavior

Testing

  • Verified the fix by checking that setStep() is still called with the correct scaled grid size
  • The drag behavior continues to work correctly at different zoom levels

Remove SCALE_X from destructuring in BaseNode render() to prevent
mobx from tracking it as a reactive dependency. This fixes the
performance issue where all nodes re-render when zooming the canvas.

Closes didi#2396
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 5, 2026

🦋 Changeset detected

Latest commit: 99fdf5f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@logicflow/core Patch
@logicflow/extension Patch
@logicflow/layout Patch
@logicflow/react-node-registry Patch
@logicflow/vue-node-registry Patch
logicflow-docs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ZivvW
Copy link
Copy Markdown
Contributor

ZivvW commented Apr 7, 2026

Thank you for submitting this pull request and proposing a solution to the issue.

However, after reviewing the change, I believe it only addresses the re-render problem when the conditional statement is not triggered. When the node is draggable (i.e., when the condition is satisfied), a re-render still occurs during canvas zoom operations.

I believe a better approach would be to move this.stepDrag.setStep out of the render function and execute it only in handleMouseDown, to keep it consistent with the other this.stepDrag.setStep calls elsewhere in the codebase:

this.stepDrag.setStep(gridSize * SCALE_X)
this.stepDrag.handleMouseDown(ev)

this.stepDrag.setStep(gridSize * SCALE_X)
this.stepDrag.handleMouseDown(ev)

Please note that this alternative solution has not been thoroughly tested yet. Additionally, I do not use snapGrid in my project.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes node view rendering during canvas zoom by attempting to prevent MobX from tracking transformModel.SCALE_X as a reactive dependency in BaseNode.render(), addressing performance regression reported in #2396.

Changes:

  • Stop destructuring SCALE_X from graphModel.transformModel in BaseNode.render().
  • Update stepDrag.setStep() to use transformModel.SCALE_X instead of a destructured SCALE_X variable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 558 to 560
if (adjustNodePosition && draggable) {
this.stepDrag.setStep(gridSize * SCALE_X)
this.stepDrag.setStep(gridSize * transformModel.SCALE_X)
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still reads the observable transformModel.SCALE_X during render(), so an observer-wrapped node view will continue to track SCALE_X and re-render on every zoom change. Switching from destructuring to transformModel.SCALE_X doesn’t avoid MobX dependency tracking; it only changes when the read occurs.

To actually prevent zoom-triggered node re-renders, avoid reading SCALE_X in render() entirely (e.g., move setStep() into handleMouseDown right before stepDrag.handleMouseDown, or wrap the read in mobx.untracked).

Copilot uses AI. Check for mistakes.
@cyphercodes
Copy link
Copy Markdown
Author

Updated this PR based on the review suggestion:

  • Moved BaseNode's scaled stepDrag.setStep(...) out of render() and into handleMouseDown(), so draggable nodes no longer read transformModel.SCALE_X during render on canvas zoom.
  • Added a focused regression test that asserts render does not update the drag step, while pointer down updates it before starting drag.
  • Added the @logicflow/core patch changeset.

Local verification:

  • git diff --check
  • pnpm --filter @logicflow/core run build:esm
  • pnpm exec jest packages/core/__tests__/view/base-node.test.tsx --runInBand --config /tmp/pr-clear-action-update/jest-local.config.cjs --no-cache

Note: direct pnpm exec jest ... --runInBand in this Node 22 environment fails before running tests because the repo's current Babel/Jest setup lacks the private-methods transform for jest-runner; the local Jest config above was only used to work around that environment issue for the targeted regression test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Report]: 画布缩放时所有节点 re-render,导致卡顿

3 participants