贝塞尔曲线(Bezier curve)实现节点连接

wls1036

背景

长久以来都想找一个画流程图的工具,有几个需求,可以将组件拖到绘图面板中,并且组件间可以通过线进行关联,在属性面板可以配置组件的属性,这里的组件可能是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;
...

总结

后续我们也会讨论如何实现节点拖拽

阅读 1.4k

yaya平台
记录yaya平台技术细节

幸福是奋斗出来的

254 声望
72 粉丝
0 条评论
你知道吗?

幸福是奋斗出来的

254 声望
72 粉丝
文章目录
宣传栏