背景
长久以来都想找一个画流程图的工具,有几个需求,可以将组件拖到绘图面板中,并且组件间可以通过线进行关联,在属性面板可以配置组件的属性,这里的组件可能是html的组件,也可能是一个功能,为什么需要这么一个东西呢?如果有这东西,很多想法就可以实现,比如
- 工作流
- 服务编排
但一直找不到满意的框架,很大原因就是这些框架颜值太低,直到遇到NodeRed,NodeRed是物联网开发工具,提供可视化界面通过配置就能实现物联网程序的开发,让我感兴趣的是NodeRed可视化界面,颜值高,操作便利,简洁不花哨
因为NodeRed是开源的,也花了一段时间去研究他的代码,想把可视化工具抽离出为我所用,种种原因失败了,既然抽离不出来,能不能自己实现一个?之前从来没想过自己开发一个,但细想觉得并不难实现,可视化界面核心有两点
- 组件的绘制
- 连接线的绘制
整个界面都是通过svg相关标签实现,组件的绘制相对比较简单,就是一些svg元素组合,卡在了连接线这里,因为NodeRed节点连接线操作平滑,曲线合理,一开始以为是用很复杂的算法实现的,在源码里去找这个算法,其实是把问题想复杂了,这个就是一条普通的贝塞尔曲线,既然知道了实现的原理,自己做一个也不难,这一篇博客就介绍如何用贝塞尔曲线实现节点连接。
贝塞尔曲线
我本身不是数学专业,对贝塞尔曲线在数学上的原理我也不懂,我觉得也没必要弄懂,就当作一个工具用,贝塞尔曲线就是可以实现两个点平滑的进行连接的一个工具,贝塞尔曲线又四个点进行控制,一个起点一个终点,两个控制点,通过对控制点的调整可以实现曲线像皮筋一样变形,达到我们想要的效果。
这里有个很棒的网站 可以进行在线绘制贝塞尔曲线
实现
从上面可以看出,两个控制点的位置决定了曲线的路径,所以这两个点的位置很重要,我查看了NodeRed的实现,NodeRed是分别选取距离起点水平75px和终点75px两个点作为控制点,以下图为例
两个节点之间距离长是200px,宽是160px,曲线的svg代码如下
<path class="link_line link_path" d="M 340 260 C 415 260 465 100 540 100"></path>
M代表起点(340,260),C后面跟着两个控制点(415,260),(465,100)最后(540,100)是终点,第一个控制点和起点Y是相同的,说明在同一个水平面上,x相距415-340=75,第二个控制点和终点也是Y相同,x相距540-465=75,就算改变组件位置,这个长度依然不会变,效果和下面这个是一样的
既然知道了规则,我们就自己实现一个,以下是距离75px的效果,基本和NodeRed一样
参考代码:
<html>
<body>
<svg id="svg" width='100%' height='100%' style="background-color: #f5f5df">
</svg>
</body>
<script type="text/javascript">
var startX = 0;
var startY = 0;
var drawable = false;
document.addEventListener('mousedown', function (event) {
startX = event.clientX;
startY = event.clientY;
drawable = true;
});
document.addEventListener('mouseup', function (event) {
drawable = false;
})
document.addEventListener('mousemove', function (event) {
if (drawable) {
let x1 = startX;
let y1 = startY;
let level=75;
let c1x = x1 + level;
let c1y = y1;
let c2x = event.clientX - level;
let c2y = event.clientY;
let path = '<path d=\'M ' + x1 + ' ' + y1 + 'C ' + c1x + ' ' + c1y + ' ' + c2x + ' ' + c2y + ' ' + event.clientX + ' ' + event.clientY + '\' style="stroke-width:3;stroke: purple;fill:none"></path>'
let p1 = '<path d="M ' + x1 + ' ' + y1 + ' L' + ' ' + (c1x - 3) + ' ' + c1y + '" style="stroke-width: 3;stroke: red;stroke-dasharray: 2 1;"></path>';
let p2 = '<path d="M ' + event.clientX + ' ' + event.clientY + ' L' + ' ' + (c2x - 3) + ' ' + c2y + '" style="stroke-width: 3;stroke: red;stroke-dasharray: 2 1;"></path>';
let c1 = '<circle cx="' + c1x + '" cy="' + c1y + '" r="5" stroke="red" stroke-width="2" fill="red" stroke-dasharray: 2 1;/>';
let c2 = '<circle cx="' + c2x + '" cy="' + c2y + '" r="5" stroke="red" stroke-width="2" fill="red" stroke-dasharray: 2 1;/>';
let p3 = path + p1 + p2 + c1 + c2;
document.getElementById("svg").innerHTML = p3;
}
})
</script>
</html>
可以尝试更改level查看不同长度的效果,以下是距离一半的效果
就修改了level的长度为距离的一半
...
let off = Math.abs(event.clientX - x1);
let level = off / 2;
...
总结
后续我们也会讨论如何实现节点拖拽
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。