跪拜 Guibai
← All articles
Frontend · JavaScript

Build a Vue3 Flowchart Editor from Scratch: Node Dragging, Bezier Curves, and Box Selection

By CoderWeen ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

For Western developers building low-code platforms, workflow engines, or data orchestration tools, this project offers a production-ready blueprint for one of the most complex UI components. The Service class pattern and event bus architecture provide a clean, maintainable approach to managing the intense interactivity that flowchart editors demand.

Summary

Building a flowchart editor from scratch is the best way to truly understand the core interactions behind low-code and workflow tools. A new open-source Vue3 project demonstrates this by implementing node dragging, Bezier curve connections, box selection, and infinite canvas features entirely from the ground up.

The project's architecture separates each complex interaction into its own Service class—DragElementNodeService, DrawLineService, RectangleSelect, and ScrollParent—keeping the Vue component clean and focused on event dispatching. A global event bus (mitt) broadcasts mouse events to all services, solving the common problem of losing drag events when the cursor leaves the node.

Key implementation details include: recording mouse offset relative to the node on drag start to prevent jumping, multi-select linkage that moves all selected nodes together with boundary protection, cubic Bezier curves that create professional-looking connections with automatic snapping to valid target points, and a rectangular selection that detects fully enclosed nodes and their associated connections for bulk operations.

Takeaways
The project uses a Service class pattern to separate each interaction (drag, connect, select, scroll) into its own file, keeping Vue components thin.
A global event bus (mitt) listens for mousemove/mouseup once and broadcasts to all services, preventing event loss when the cursor leaves a node.
Node dragging records the mouse offset relative to the node's top-left corner on mousedown, preventing the node from jumping to the cursor position.
Multi-select dragging calculates a delta and moves all selected nodes together, with boundary protection that cancels the move if any node would go out of bounds.
Connections use cubic Bezier curves with control points positioned below the start and left of the end, creating a smooth 'down then right' visual flow.
Automatic snapping validates target connection points against rules: no self-connections, no left-to-left connections, and at most one connection per node pair.
Box selection uses collision detection to find nodes fully within the rectangle, then also selects connections whose source nodes are selected.
Canvas auto-scrolls when dragging or selecting near the viewport edge, with a global flag to prevent accidental scrolling.
Data persistence uses a single watch on the entire canvas state, writing to localStorage with toRaw() for safe serialization.
The project is open-source on GitHub at Fate-ui/flowChart.
Conclusions

The Service class pattern is a pragmatic alternative to complex state machines for managing multi-interaction UI components—each service owns a slice of the interaction logic and communicates via events.

Recording mouse offset on drag start is a small detail that dramatically improves UX; many amateur implementations skip this and cause node jumping.

The decision to use a single global event listener rather than per-service listeners is a smart tradeoff—it prevents event loss during fast interactions and centralizes event management.

Bezier curve control point placement is an underappreciated design decision; the 'down then left' approach here mimics how professional tools like draw.io handle connections.

The connection rule validation (no self-connections, type matching, single connection per pair) shows that even a 'simple' flowchart needs a surprising amount of business logic.

Using toRaw() before serializing Vue reactive data is a best practice that many developers overlook, leading to serialization errors with Proxy objects.

The auto-scroll implementation with a global flag is a clean solution to a common problem—preventing scroll interference when the user is just hovering near the edge.

Concepts & terms
Service Class Pattern
An architectural pattern where each complex interaction (drag, connect, select) is encapsulated in its own class, keeping Vue components thin and logic modular.
Event Bus (mitt)
A lightweight publish-subscribe pattern using the mitt library to broadcast mouse events from a single listener to multiple service classes, preventing event loss.
Cubic Bezier Curve
A smooth curve defined by two endpoints and two control points. Used here to create natural-looking connection lines between flowchart nodes.
tryOnScopeDispose
A VueUse utility that automatically runs cleanup code when a Vue component's scope is destroyed, preventing memory leaks from event listeners.
toRaw
A Vue API that returns the raw, non-reactive object from a reactive Proxy, necessary for safe JSON serialization of Vue reactive state.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗