背景
長久以來都想找一個畫流程圖的工具,有幾個需求,可以將元件拖到繪圖皮膚中,並且元件間可以透過線進行關聯,在屬性皮膚可以配置元件的屬性,這裡的元件可能是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;
...
總結
後續我們也會討論如何實現節點拖拽