jsPlumb + jQuery 流程图设计器:拖拽连线与序列化实战
简介这是一份面向Web前端开发者与工作流设计人员的jsPlumbjQuery流程图设计实例资源适合希望快速掌握可视化连线、节点拖拽与动态流程图搭建的初中级开发者。压缩包共216个文件约984KB以148张png截图、24个js脚本、20个gif动图为主辅以css样式、config配置及少量cs、aspx等源码文件完整呈现了一个可运行的流程图演示项目。资源围绕jsPlumb的端点创建、连接器样式、事件监听与jQuery的DOM操作展开包含Bezier曲线、箭头覆盖物、拖放交互等典型实现并附带flowchartDemo-master示例工程便于读者对照源码理解节点连接、连接创建与删除事件、自定义样式及动画扩展等关键技巧。目前已有725人学习下载适合作为流程图组件开发、业务拓扑图搭建的入门参考与二次开发基础。1. 从一张审批单说起为什么 jsPlumb jQuery 仍是流程图设计的高性价比方案很多做后台系统的同学都遇到过这样的需求业务方要一个能拖拽节点、连线、保存拓扑关系的流程图设计器用来配置审批流、任务依赖或者数据管道。第一反应往往是上 React Flow 或者 G6但真到落地时才发现老项目是 jQuery 技术栈引入一整套前端框架的成本比想象中高得多。这时候 jsPlumb jQuery 的组合就显出价值了——它不要求你重构整个页面只要在现有 DOM 上挂载连线能力几百行代码就能跑出一个可用的流程图设计实例。这套方案特别适合课程设计、毕设里需要「可视化配置」模块的场景也适合维护存量 jQuery 项目的工程师快速交付一个流程图编辑器。核心要解决的问题就三个节点可拖拽、连线可建立与删除、拓扑结构可序列化保存。2. 需求拆解与技术选型为什么是 jsPlumb 而不是 Canvas 自绘2.1 功能边界先划清楚在动手写代码之前我一般会先把流程图设计器的功能边界列出来避免做到一半发现漏了关键能力。这个实例需要覆盖的核心功能包括左侧物料区提供三种节点类型开始、处理、结束拖拽到画布生成节点节点在画布内可自由拖动拖动时连线跟随移动节点边缘有连接锚点从一个节点拖到另一个节点建立连线连线可选中并删除节点删除时关联连线一并清除画布支持保存为 JSON 结构包含节点坐标、类型、ID 和连线关系支持从 JSON 反向渲染用于编辑已有流程这些功能看起来简单但每一项都有坑。比如节点拖动时连线跟随如果用 Canvas 自绘需要自己算贝塞尔曲线控制点用 jsPlumb 的话它内部已经处理了重绘逻辑你只需要调用repaint方法。2.2 技术选型对比Canvas 自绘 vs SVG vs jsPlumb选型这件事我的习惯是先看团队技术栈和交付周期。如果项目本身就是 jQuery 体系硬上 React Flow 意味着要引入构建工具、状态管理、组件通信一整套东西对于一个小型流程图模块来说属于过度设计。下面这张表是我在几个方案之间做的对比方案开发成本与 jQuery 兼容性连线交互序列化难度Canvas 自绘高好需自己实现中SVG 手写中高好需自己实现中jsPlumb jQuery低极好内置低React Flow中差内置低jsPlumb 的本质是在 DOM 元素上叠加一层 SVG 或 Canvas 来绘制连线节点本身还是普通 div所以和 jQuery 的拖拽、事件绑定天然契合。它的社区版功能已经覆盖了流程图设计器 90% 的需求包括锚点、端点、连接器样式、拖拽重绘等。2.3 依赖引入与初始化配置引入 jsPlumb 有两种方式直接 script 标签引入或者通过 npm 安装。对于 jQuery 项目我通常直接用 script 标签避免构建工具的折腾。!-- 先引入 jQuery再引入 jsPlumb -- script srchttps://cdn.jsdelivr.net/npm/jquery3.7.1/dist/jquery.min.js/script script srchttps://cdn.jsdelivr.net/npm/jsplumb2.15.6/dist/jsplumb.min.js/script初始化 jsPlumb 实例时有几个关键配置项需要提前设好否则后面改起来很麻烦// 初始化 jsPlumb 实例绑定到画布容器 var instance jsPlumb.getInstance({ Container: flowCanvas, // 画布容器 ID所有连线绘制在这个容器内 Connector: [Bezier, { curviness: 50 }], // 连线样式贝塞尔曲线弯曲度 50 Endpoint: [Dot, { radius: 4 }], // 端点样式圆点半径 4px Anchor: Continuous, // 锚点自动选择最近边缘 PaintStyle: { stroke: #456, strokeWidth: 2 }, // 连线颜色和粗细 HoverPaintStyle: { stroke: #2a7, strokeWidth: 3 }, // 悬停高亮 ConnectionOverlays: [ // 连线上的叠加元素 [Arrow, { location: 1, width: 10, length: 10 }] // 箭头 ] });这里Container必须指定否则连线会挂到 body 上滚动页面时连线位置会错乱。Anchor: Continuous让 jsPlumb 自动选择节点最近的边缘作为连接点省去手动指定锚点位置的麻烦。ConnectionOverlays里的箭头配置让连线有方向感审批流这类场景必须要有。3. 总体架构设计四个模块撑起一个流程图编辑器3.1 模块划分与职责整个流程图设计器我拆成四个模块每个模块职责单一方便调试和替换物料区模块负责渲染可拖拽的节点模板处理拖拽起始事件把节点类型传递给画布画布模块负责接收拖拽生成节点、管理节点 DOM、初始化 jsPlumb 端点连线管理模块负责建立连线、删除连线、校验连线合法性比如开始节点不能有入边数据序列化模块负责把画布状态导出为 JSON以及从 JSON 恢复画布这四个模块之间通过自定义事件通信而不是直接互相调用。比如物料区拖拽结束时触发node:drop事件画布模块监听这个事件并创建节点。这样做的好处是模块可以独立测试也方便后续替换某个模块的实现。3.2 节点数据结构设计节点和连线的数据结构直接决定了序列化的难易程度。我一般用下面这种扁平结构// 节点对象 { id: node_1, // 唯一标识用时间戳或递增数字 type: process, // 节点类型start / process / end label: 审批, // 显示文本 x: 300, // 相对画布的 X 坐标 y: 150 // 相对画布的 Y 坐标 } // 连线对象 { id: conn_1, // 连线唯一标识 source: node_1, // 起点节点 ID target: node_2, // 终点节点 ID sourceAnchor: [0.5, 1], // 起点锚点位置可选用于精确恢复 targetAnchor: [0.5, 0] // 终点锚点位置 }保存时把节点数组和连线数组一起序列化恢复时先创建节点 DOM再根据连线数据调用instance.connect重建连线。这里有个细节恢复连线时如果锚点位置不保存jsPlumb 会重新计算可能导致连线走向和保存前不一致。所以我在连线对象里额外存了锚点坐标。3.3 画布与物料区的 DOM 结构DOM 结构尽量简单避免深层嵌套导致拖拽计算复杂div classflow-editor !-- 左侧物料区 -- div classpalette div classpalette-item>// 物料区拖拽开始把节点类型写入 dataTransfer $(.palette-item).on(dragstart, function (e) { e.originalEvent.dataTransfer.setData(nodeType, $(this).data(type)); }); // 画布允许放置 $(#flowCanvas).on(dragover, function (e) { e.preventDefault(); // 必须阻止默认行为才能触发 drop }); // 画布放置时创建节点 $(#flowCanvas).on(drop, function (e) { e.preventDefault(); var type e.originalEvent.dataTransfer.getData(nodeType); if (!type) return; // 计算节点在画布内的坐标 var offset $(this).offset(); var x e.originalEvent.clientX - offset.left - 40; // 减去节点半宽让节点居中 var y e.originalEvent.clientY - offset.top - 20; createNode(type, x, y); });createNode函数负责生成节点 DOM 并注册 jsPlumb 端点var nodeIdCounter 0; function createNode(type, x, y) { var id node_ (nodeIdCounter); var label { start: 开始, process: 处理, end: 结束 }[type]; // 创建节点 DOM var $node $(div classflow-node/div) .attr(id, id) .addClass(node- type) .css({ left: x, top: y }) .text(label); $(#flowCanvas).append($node); // 注册 jsPlumb 端点源端和目标端都注册允许双向连线 instance.addEndpoint(id, { uuid: id _source, anchor: Continuous, isSource: true, isTarget: true, maxConnections: -1 // -1 表示不限制连接数 }); // 节点自身可拖动 instance.draggable(id, { containment: parent, // 限制在画布内拖动 grid: [10, 10] // 拖动时按 10px 网格吸附对齐更整齐 }); return id; }这里instance.draggable是 jsPlumb 提供的拖拽方法它内部用 jQuery UI 的 draggable 实现但不需要你额外引入 jQuery UI。containment: parent确保节点不会被拖出画布grid让节点对齐到网格视觉上更规整。4.2 连线建立与删除连线建立是 jsPlumb 自动处理的你只需要在初始化时配置好端点。但删除连线需要手动绑定事件// 连线建立后触发可以在这里做合法性校验 instance.bind(connection, function (info) { var sourceType $(# info.sourceId).data(type); var targetType $(# info.targetId).data(type); // 开始节点不能有入边结束节点不能有出边 if (targetType start || sourceType end) { instance.deleteConnection(info.connection); alert(开始节点不能作为终点结束节点不能作为起点); return; } // 保存连线数据到内存 connectionList.push({ id: conn_ info.connection.id, source: info.sourceId, target: info.targetId }); }); // 点击连线选中按 Delete 键删除 instance.bind(click, function (conn, originalEvent) { if (confirm(确定删除这条连线吗)) { instance.deleteConnection(conn); // 从内存中移除 connectionList connectionList.filter(function (c) { return c.id ! conn_ conn.id; }); } });connection事件的info参数包含sourceId、targetId和connection对象。合法性校验放在这里最合适因为此时连线已经建立但还未持久化删除操作对用户来说是无感的。click事件绑定在 instance 上点击连线时触发用confirm确认删除避免误操作。4.3 序列化与反序列化保存功能把节点和连线数据导出为 JSONfunction saveFlow() { var nodes []; $(#flowCanvas .flow-node).each(function () { var $node $(this); nodes.push({ id: $node.attr(id), type: $node.data(type), label: $node.text(), x: parseInt($node.css(left)), y: parseInt($node.css(top)) }); }); var data { nodes: nodes, connections: connectionList }; // 输出到控制台或发送到后端 console.log(JSON.stringify(data, null, 2)); return data; }恢复时先清空画布再按顺序重建function loadFlow(data) { // 清空画布和 jsPlumb 实例 instance.reset(); $(#flowCanvas).empty(); connectionList []; nodeIdCounter 0; // 先创建所有节点 data.nodes.forEach(function (node) { var id createNode(node.type, node.x, node.y); // 恢复节点 ID 和标签 $(# id).attr(id, node.id).text(node.label); // 更新 nodeIdCounter避免后续创建节点 ID 冲突 var num parseInt(node.id.replace(node_, )); if (num nodeIdCounter) nodeIdCounter num; }); // 再重建连线 data.connections.forEach(function (conn) { instance.connect({ source: conn.source, target: conn.target, anchors: [conn.sourceAnchor || Continuous, conn.targetAnchor || Continuous] }); connectionList.push(conn); }); }这里有个容易翻车的点instance.reset()会清除所有端点和连线但不会清除 DOM 节点所以需要手动empty()。另外节点 ID 恢复后nodeIdCounter要同步更新否则下次创建节点时 ID 会重复导致 jsPlumb 端点冲突。5. 避坑与常见问题排查那些让我加班到凌晨的细节5.1 连线位置偏移或错乱现象拖动节点后连线不跟随或者连线整体偏移到画布外。原因画布容器没有设置position: relativejsPlumb 的连线层以 body 为定位基准。或者画布有滚动条时没有调用instance.repaintEverything()。解决给画布容器加position: relative并在画布滚动或窗口 resize 时调用instance.repaintEverything()。如果画布内有滚动还需要在滚动事件里重新计算偏移。5.2 节点拖动时连线闪烁现象拖动节点过程中连线频繁重绘导致闪烁。原因jsPlumb 默认在拖动时实时重绘如果节点数量多性能跟不上。解决在instance.draggable的配置里加drag: function () { instance.repaintEverything(); }或者降低重绘频率。更彻底的办法是拖动时隐藏连线拖动结束后再显示但体验会打折扣。5.3 序列化后恢复连线锚点位置不对现象保存前连线从节点右侧出发恢复后变成从底部出发。原因Anchor: Continuous是动态计算的恢复时节点位置和保存时一致但 jsPlumb 内部状态不同导致锚点重新计算。解决在连线数据里保存sourceAnchor和targetAnchor的具体坐标恢复时用anchors参数指定而不是用Continuous。5.4 删除节点后连线残留现象删除节点 DOM 后连线还在画布上。原因只删了 DOM没有调用instance.remove清除 jsPlumb 内部记录。解决删除节点时先调用instance.remove(nodeId)再移除 DOM。instance.remove会自动删除该节点关联的所有连线和端点。5.5 多个 jsPlumb 实例冲突现象页面上有多个画布时连线跑到另一个画布上。原因jsPlumb 默认是单例模式多次getInstance返回同一个实例。解决用jsPlumb.getInstance创建独立实例并给每个实例指定不同的Container。如果确实需要多个实例确保它们的容器 ID 不重复。6. 验收与答辩怎么证明你的流程图设计器是合格的验收这个流程图设计器我一般从五个维度来检查每个维度都有具体的操作步骤和预期结果验收项操作步骤预期结果节点生成从物料区拖拽三种节点到画布节点出现在鼠标释放位置样式正确节点拖动拖动任意节点到画布另一位置节点跟随鼠标连线实时重绘不超出画布连线建立从一个节点端点拖到另一个节点生成带箭头的贝塞尔曲线方向正确连线删除点击连线确认删除连线消失节点不受影响序列化点击保存刷新页面后加载 JSON节点位置、类型、连线关系完全恢复答辩时评委最常问的三个问题是为什么选 jsPlumb 而不是其他方案、连线合法性怎么校验、数据怎么持久化。第一个问题用第 2 章的选型对比表回答第二个问题现场演示「开始节点不能有入边」的校验逻辑第三个问题展示 JSON 结构并说明可以存到后端数据库。我自己的习惯是每次交付前都会把上面这张表跑一遍尤其是序列化恢复这一项因为它是整个方案里最容易出问题的地方。有一次我自信满满地演示结果恢复后连线全跑到画布外面了原因就是忘了在loadFlow里调用instance.repaintEverything()。从那以后我每次保存和加载都强制走一遍完整流程确认连线位置和保存前一致才敢提交。希望这套方案能帮到你少走一些我踩过的弯路。本文还有配套的精品资源点击获取

相关新闻

H5 AI聊天流式响应实战:SSE解析、打字机渲染与性能优化

H5 AI聊天流式响应实战:SSE解析、打字机渲染与性能优化

1. 别急着接SDK:先搞清楚H5里的AI流式响应到底卡在哪做AI聊天功能,很多前端朋友第一次接后端流式接口时都会遇到同一个困惑:明明后端数据是一段一段吐出来的,自己也用了EventSource或者fetch的ReadableStream,页面上的…

2026/10/19 2:46:51 阅读更多 →
腾讯云人脸识别API实战:刷脸门禁系统从方案到踩坑全指南

腾讯云人脸识别API实战:刷脸门禁系统从方案到踩坑全指南

前阵子有人找我,说他们团队做了一个带门禁功能的管理系统,想让摄像头“认识”每一个进门的员工。需求一句话就能讲完:刷脸开门。但真正落地的时候,从选方案、调阈值到处理各种照片过不了、活体被绕过的问题,远比想象中…

2026/10/19 2:46:51 阅读更多 →
RIS辅助多用户MIMO系统MATLAB仿真:从信道建模到SBL-GAMP估计

RIS辅助多用户MIMO系统MATLAB仿真:从信道建模到SBL-GAMP估计

简介:面向计算机、电子信息工程与数学等专业学生,这套Matlab仿真工程围绕可重构智能表面(RIS)辅助多用户MIMO系统展开,重点实现无源波束成形与信息传输的联合设计,可直接用于课程设计、期末大作业或毕业设计…

2026/10/19 2:46:51 阅读更多 →

最新新闻

ARM 汇编 LDR 指令与 LDR 伪指令完整详解

ARM 汇编 LDR 指令与 LDR 伪指令完整详解

一、概念总述ARM 汇编中 LDR 存在两种完全不同身份,书写格式是核心区分依据:CPU 原生硬件指令 LDR:格式含方括号 [],硬件原生支持,功能为从内存读取数据到寄存器;汇编伪指令 LDR:格式含等号 &am…

2026/10/19 3:39:15 阅读更多 →
79通道ResNet改造:阿尔茨海默症MRI切片诊断模型

79通道ResNet改造:阿尔茨海默症MRI切片诊断模型

简介:本资源是一份面向深度学习初学者与课程实践者的阿尔茨海默症医学影像识别项目,聚焦ResNet模型优化与多维医学图像建模能力培养,适用于本科课程设计、工程实训及毕设选题参考。压缩包共13个文件,含6个核心Python脚本&#xff…

2026/10/19 3:39:15 阅读更多 →
AD早期识别三模态ResNet实战:2D/3D/MedicalNet医学影像分析

AD早期识别三模态ResNet实战:2D/3D/MedicalNet医学影像分析

简介:本资源是一份面向深度学习初学者与课程设计实践者的阿尔茨海默症医学影像识别项目,聚焦ResNet模型优化与多维建模方法,适用于本科课程设计、大作业及毕设前期技术验证。压缩包共13个文件,含6个核心Python脚本(实现…

2026/10/19 3:39:15 阅读更多 →
阿尔茨海默症MRI识别:ResNet结构优化与临床可解释性设计

阿尔茨海默症MRI识别:ResNet结构优化与临床可解释性设计

简介:本资源是一份面向深度学习初学者与课程实践者的阿尔茨海默症医学影像识别项目,聚焦ResNet模型优化与多维医学图像建模能力培养,适用于本科课程设计、工程实训及毕设选题参考。压缩包共13个文件,含6个核心Python脚本&#xff…

2026/10/19 3:39:15 阅读更多 →
Pi 1.0到1.1:从严格拒绝到智能接受的演进与实操指南

Pi 1.0到1.1:从严格拒绝到智能接受的演进与实操指南

1. 从“拒绝一切”到“选择性接受”:这个工具到底经历了什么第一次接触 Pi 这个工具的人,大概率会被它的“脾气”整懵。你兴冲冲地写了一段配置,点击运行,它回你一句冷冰冰的“不支持”;你换了个写法,它又告…

2026/10/19 3:39:15 阅读更多 →
开源代码审查新范式:CLI驱动的Git Diff+LLM协作框架

开源代码审查新范式:CLI驱动的Git Diff+LLM协作框架

1. 这不是又一个“AI代码审查工具”,而是一套可落地的开源协作新范式“open-code-review”这四个字乍看像某个GitHub仓库名,但背后藏着的是当前工程团队在AI时代最真实、最迫切的一次工作流重构。它既不是单纯调用某个大模型API的玩具脚本,也…

2026/10/19 3:38:14 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →