name: svelteflow-fundamentals user-invocable: false description: 使用Svelte Flow构建节点基础的UI、流程图、工作流编辑器或交互式图表。涵盖设置、节点、边缘、控件和交互性。 allowed-tools:
- Bash
- Read
Svelte Flow 基础知识
使用Svelte Flow构建可自定义的节点基础编辑器和交互式图表。 这个技能涵盖核心概念、设置和创建Svelte应用中流程基础界面的常见模式。
安装
# npm
npm install @xyflow/svelte
# pnpm
pnpm add @xyflow/svelte
# yarn
yarn add @xyflow/svelte
基本设置
<script lang="ts">
import {
SvelteFlow,
Controls,
Background,
MiniMap,
type Node,
type Edge,
} from '@xyflow/svelte';
import { writable } from 'svelte/store';
import '@xyflow/svelte/dist/style.css';
const nodes = writable<Node[]>([
{
id: '1',
type: 'input',
data: { label: '开始' },
position: { x: 250, y: 0 },
},
{
id: '2',
data: { label: '处理' },
position: { x: 250, y: 100 },
},
{
id: '3',
type: 'output',
data: { label: '结束' },
position: { x: 250, y: 200 },
},
]);
const edges = writable<Edge[]>([
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
]);
</script>
<div style="height: 100vh; width: 100vw;">
<SvelteFlow {nodes} {edges} fitView>
<Controls />
<Background />
<MiniMap />
</SvelteFlow>
</div>
使用存储管理状态
<script lang="ts">
import { SvelteFlow, type Node, type Edge } from '@xyflow/svelte';
import { writable, derived } from 'svelte/store';
// 可写存储用于反应式状态
const nodes = writable<Node[]>([
{ id: '1', data: { label: '节点 1' }, position: { x: 0, y: 0 } },
{ id: '2', data: { label: '节点 2' }, position: { x: 200, y: 100 } },
]);
const edges = writable<Edge[]>([
{ id: 'e1-2', source: '1', target: '2' },
]);
// 派生存储用于计算值
const nodeCount = derived(nodes, ($nodes) => $nodes.length);
const edgeCount = derived(edges, ($edges) => $edges.length);
// 添加新节点
function addNode() {
const id = `node-${Date.now()}`;
nodes.update((n) => [
...n,
{
id,
data: { label: `节点 ${$nodeCount + 1}` },
position: { x: Math.random() * 300, y: Math.random() * 300 },
},
]);
}
// 更新节点标签
function updateNodeLabel(id: string, label: string) {
nodes.update((n) =>
n.map((node) =>
node.id === id ? { ...node, data: { ...node.data, label } } : node
)
);
}
// 删除节点
function deleteNode(id: string) {
nodes.update((n) => n.filter((node) => node.id !== id));
// 同时移除连接的边缘
edges.update((e) => e.filter((edge) => edge.source !== id && edge.target !== id));
}
</script>
<div>
<p>节点: {$nodeCount} | 边缘: {$edgeCount}</p>
<button on:click={addNode}>添加节点</button>
</div>
<SvelteFlow {nodes} {edges} fitView />
节点类型
内置节点类型
<script lang="ts">
import { SvelteFlow, type Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
const nodes = writable<Node[]>([
// 输入节点 - 只有源句柄
{
id: '1',
type: 'input',
data: { label: '输入节点' },
position: { x: 0, y: 0 },
},
// 默认节点 - 有源句柄和目标句柄
{
id: '2',
type: 'default',
data: { label: '默认节点' },
position: { x: 0, y: 100 },
},
// 输出节点 - 只有目标句柄
{
id: '3',
type: 'output',
data: { label: '输出节点' },
position: { x: 0, y: 200 },
},
]);
</script>
节点配置
import { Position, type Node } from '@xyflow/svelte';
const node: Node = {
id: 'unique-id',
type: 'default',
position: { x: 100, y: 100 },
data: { label: '我的节点', customProp: 'value' },
// 可选属性
style: 'background-color: #f0f0f0;',
class: 'custom-node',
sourcePosition: Position.Right,
targetPosition: Position.Left,
draggable: true,
selectable: true,
connectable: true,
deletable: true,
hidden: false,
selected: false,
dragging: false,
zIndex: 0,
extent: 'parent',
parentId: 'parent-node-id',
expandParent: true,
};
边缘类型和配置
<script lang="ts">
import { SvelteFlow, MarkerType, type Edge } from '@xyflow/svelte';
import { writable } from 'svelte/store';
const edges = writable<Edge[]>([
// 默认边缘(贝塞尔曲线)
{
id: 'e1',
source: '1',
target: '2',
type: 'default',
},
// 直线
{
id: 'e2',
source: '2',
target: '3',
type: 'straight',
},
// 步进边缘(直角)
{
id: 'e3',
source: '3',
target: '4',
type: 'step',
},
// 平滑步进边缘(圆角)
{
id: 'e4',
source: '4',
target: '5',
type: 'smoothstep',
},
// 带箭头的样式边缘
{
id: 'e5',
source: '5',
target: '6',
label: '连接',
style: 'stroke: #333; stroke-width: 2px;',
animated: true,
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#333',
},
},
]);
</script>
事件处理
<script lang="ts">
import { SvelteFlow, type Node, type Edge } from '@xyflow/svelte';
import { writable } from 'svelte/store';
const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
// 节点事件
function handleNodeClick(event: CustomEvent<{ node: Node }>) {
console.log('节点点击:', event.detail.node);
}
function handleNodeDoubleClick(event: CustomEvent<{ node: Node }>) {
console.log('节点双击:', event.detail.node);
}
function handleNodeDragStart(event: CustomEvent<{ node: Node }>) {
console.log('拖动开始:', event.detail.node.id);
}
function handleNodeDragStop(event: CustomEvent<{ node: Node }>) {
console.log('拖动停止:', event.detail.node.position);
}
// 边缘事件
function handleEdgeClick(event: CustomEvent<{ edge: Edge }>) {
console.log('边缘点击:', event.detail.edge);
}
// 连接事件
function handleConnect(event: CustomEvent<{ connection: any }>) {
const { connection } = event.detail;
edges.update((e) => [
...e,
{
id: `${connection.source}-${connection.target}`,
source: connection.source,
target: connection.target,
},
]);
}
// 选择事件
function handleSelectionChange(
event: CustomEvent<{ nodes: Node[]; edges: Edge[] }>
) {
console.log('选择的节点:', event.detail.nodes);
console.log('选择的边缘:', event.detail.edges);
}
</script>
<SvelteFlow
{nodes}
{edges}
on:nodeclick={handleNodeClick}
on:nodedoubleclick={handleNodeDoubleClick}
on:nodedragstart={handleNodeDragStart}
on:nodedragstop={handleNodeDragStop}
on:edgeclick={handleEdgeClick}
on:connect={handleConnect}
on:selectionchange={handleSelectionChange}
fitView
/>
插件组件
背景
<script>
import { SvelteFlow, Background, BackgroundVariant } from '@xyflow/svelte';
</script>
<SvelteFlow {nodes} {edges}>
<!-- 点图案 -->
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
<!-- 线图案 -->
<Background variant={BackgroundVariant.Lines} gap={20} />
<!-- 交叉图案 -->
<Background variant={BackgroundVariant.Cross} gap={25} />
<!-- 自定义颜色 -->
<Background color="#aaa" gap={16} />
</SvelteFlow>
控件
<script>
import { SvelteFlow, Controls } from '@xyflow/svelte';
</script>
<SvelteFlow {nodes} {edges}>
<Controls
showZoom={true}
showFitView={true}
showInteractive={true}
position="bottom-left"
/>
</SvelteFlow>
迷你地图
<script>
import { SvelteFlow, MiniMap } from '@xyflow/svelte';
</script>
<SvelteFlow {nodes} {edges}>
<MiniMap
nodeColor={(node) => {
switch (node.type) {
case 'input':
return '#0041d0';
case 'output':
return '#ff0072';
default:
return '#1a192b';
}
}}
nodeStrokeWidth={3}
zoomable
pannable
/>
</SvelteFlow>
面板
<script>
import { SvelteFlow, Panel } from '@xyflow/svelte';
</script>
<SvelteFlow {nodes} {edges}>
<Panel position="top-left">
<button on:click={saveFlow}>保存</button>
<button on:click={restoreFlow}>恢复</button>
</Panel>
<Panel position="top-right">
<p>节点数量: {$nodes.length}</p>
</Panel>
</SvelteFlow>
使用useSvelteFlow控制视口
<script lang="ts">
import { SvelteFlow, useSvelteFlow, Panel } from '@xyflow/svelte';
const { zoomIn, zoomOut, fitView, setCenter, getViewport } = useSvelteFlow();
function handleFitView() {
fitView({ padding: 0.2 });
}
function handleCenter() {
setCenter(0, 0, { zoom: 1 });
}
function logViewport() {
const viewport = getViewport();
console.log('当前视口:', viewport);
}
</script>
<SvelteFlow {nodes} {edges}>
<Panel position="top-left">
<button on:click={() => zoomIn()}>放大</button>
<button on:click={() => zoomOut()}>缩小</button>
<button on:click={handleFitView}>适应视图</button>
<button on:click={handleCenter}>居中</button>
<button on:click={logViewport}>记录视口</button>
</Panel>
</SvelteFlow>
节点操作
<script lang="ts">
import { SvelteFlow, useSvelteFlow, type Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
const { getNodes, setNodes, addNodes, deleteElements } = useSvelteFlow();
function addNewNode() {
const id = `node-${Date.now()}`;
addNodes([
{
id,
data: { label: '新节点' },
position: { x: Math.random() * 300, y: Math.random() * 300 },
},
]);
}
function deleteSelectedNodes() {
const selectedNodes = getNodes().filter((node) => node.selected);
deleteElements({ nodes: selectedNodes });
}
function updateAllLabels(prefix: string) {
setNodes((nodes) =>
nodes.map((node, index) => ({
...node,
data: { ...node.data, label: `${prefix} ${index + 1}` },
}))
);
}
</script>
保存和恢复状态
<script lang="ts">
import { SvelteFlow, useSvelteFlow, Panel, type Viewport } from '@xyflow/svelte';
import { writable, get } from 'svelte/store';
const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
const { toObject, setViewport } = useSvelteFlow();
function saveFlow() {
const flow = toObject();
localStorage.setItem('flow', JSON.stringify(flow));
console.log('流程已保存');
}
function restoreFlow() {
const saved = localStorage.getItem('flow');
if (saved) {
const flow = JSON.parse(saved);
nodes.set(flow.nodes || []);
edges.set(flow.edges || []);
if (flow.viewport) {
setViewport(flow.viewport);
}
console.log('流程已恢复');
}
}
</script>
<SvelteFlow {nodes} {edges}>
<Panel position="top-right">
<button on:click={saveFlow}>保存</button>
<button on:click={restoreFlow}>恢复</button>
</Panel>
</SvelteFlow>
连接验证
<script lang="ts">
import { SvelteFlow, type IsValidConnection } from '@xyflow/svelte';
const isValidConnection: IsValidConnection = (connection) => {
// 防止自连接
if (connection.source === connection.target) {
return false;
}
// 添加自定义验证逻辑
return true;
};
</script>
<SvelteFlow {nodes} {edges} {isValidConnection} />
何时使用此技能
使用 svelteflow-fundamentals 当您需要:
- 使用Svelte构建工作流构建器
- 创建数据管道可视化
- 设计状态机图
- 构建聊天机器人对话流
- 创建组织图
- 构建机器学习管道可视化器
- 在Svelte应用中创建交互式决策树
最佳实践
- 使用Svelte存储管理反应式流状态
- 保持节点组件专注和可重用
- 使用TypeScript确保类型安全
- 在派生存储中记忆昂贵的计算
- 使用CSS变量进行主题化
- 为高级用户添加键盘快捷键
- 实现撤销/重做以提高用户体验
- 在初始渲染时使用fitView()