作者:AlloyTeam
網址:http://www.alloyteam.com/2015/08/shi-yong-js-shi-xian-si-wei-dao-tu/
本文主要闡述使用js實現思維導圖的關鍵技術點,如果還不知道什麼是思維導圖的同學,請自行度娘。以下是demo和原始碼的傳送門:
demo:http://rockyren.github.io/mindmaptree/
原始碼:http://github.com/RockyRen/mindmaptree/tree/master
在原始碼中我使用了svg繪製思維導圖。與canvas相比,svg將影象當成物件,我們可將思維導圖中節點和線等圖形表現為物件,而且svg更適合用於動態互動的應用
下麵介紹幾個關鍵技術點:
子節點位置的重繪
一個基本的思維導圖工具應該擁有增加節點和刪除節點的功能。在某個節點上增刪節點時,為了使得所有子節點的高度相對於該節點垂直居中,都會重新渲染子節點的垂直位置。
如圖1所示,首先求得父節點的中心點F的坐標為(hfx, hfy),設父節點與子節點的水平距離為interval,父節點的寬為parentWidth。作水平線段FC,C點的橫坐標即為子節點的橫坐標childX。如下圖所示:
為了讓子節點間垂直隔開,每一個子節點上下都有補白,所以一個子節點所佔的區域高度為該子節點的節點高度加上兩個補白高度。迭代所有子節點,求取所有子節點的區域高度areaHeight,然後線上段FC的C點上作一條長度為areaHeight的垂直平分線AB,所有子節點的垂直區域都在垂直平分線AB內,這樣可以保證所有子節點的高度相對於該節點垂直居中。如下圖所示:
我們需要求得每一個子節點的垂直坐標childY。首先求得A點的垂直坐標startY = hfy – areaHeight / 2,第一個子節點的垂直坐標由startY加padding可得。求第二個子節點的垂直坐標時,startY累加上一個子節點的區域高度,則第二個子節點的垂直坐標等於當前startY加上padding。之後的子節點透過迭代相同的操作可得。在每一輪迭代中,根據求得的子節點坐標(childX, childY)渲染節點的位置。如下圖所示:
實現程式碼如下:
// 以下變數請自行求得
var hfx, // 父節點的中心x軸坐標
hfy, // 父節點的中心y軸坐標
parentWidth, // 父節點的寬度
children, // 子節點串列
padding, // 子節點垂直間距
interval; // 節點間水平間距
var childX, // 子節點的x軸坐標
startY, // 子節點區域的起始坐標
childrenAreaHeight = 0; // 子節點總區域高度
childX = hfx + parentWidth / 2 + interval;
// 迭代子節點,求得子節點總區域高度
children.forEach(function(child){
var curAreaHeight = getNodeHeight(child) + padding * 2;
childrenAreaHeight += curAreaHeight;
});
startY = hfy – childrenAreaHeight / 2;
// 迭代子節點,求得每個子節點的垂直坐標
children.forEach(function(child){
var childY = startY + padding;
// 已經求得當前子節點坐標(childX, childY),在這裡作渲染操作
var curAreaHeight = getNodeHeight(child) + padding * 2;
startY += curAreaHeight; // 其實高度累加
});
/**
* 獲取節點的高度
*/
function getNodeHeight(){
// …
}
祖先節點的同級節點的垂直位置調整
如下圖所示,當增加一個節點時,該節點父節點的同級節點需要被“撐開”:設該節點的1/2區域高度為moveY,在父節點的同級節點中,比父節點高的向上偏移一個moveY,比父節點低的向下偏移一個moveY。父節點的父節點的同級節點也做相同的處理,一直遞迴到根節點為止。當刪除一個節點時,節點的父節點的同級節點會被“壓低”,“壓低”操作和上述操作相似。註意,當增加第一個子節點和刪除最後一個子節點時,不會進行“撐開”和“壓低”操作。
實現原始碼如下:
**
* 調整當前的父節點的同級節點的位置
* @param node 當前的父節點, 以下為該節點需要用到的屬性
* node.father: 節點的父節點,為null時表示父節點為根節點
* node.children: 節點的子節點串列
* node.x: 節點的x軸坐標
* node.y: 節點的y軸坐標
*
* @oaram areaHeight 被操作節點的區域高度
*/
function resetBrotherPosition(node, areaHeight){
var brother, // 同級節點
moveY = areaHeight / 2; // 需要移動的高度
if(node.father){
node.father.children.forEach(function(curNode){
// 遍歷同級節點
if(curNode != node){
if(brother.y < node.y){
// 向上移動brother節點的程式碼寫在這
}
else {
// 向下移動brother節點的程式碼寫在這
}
}
}
);
}
// 遞迴父節點
if(node.father){
resetBrotherPosition(node.father, areaHeight);
}
}
拖動節點
當拖動根節點時,透過改變svg的視口坐標來實現拖動整個思維導圖的效果。當拖動非根節點時,會按順序觸發mouseup、mousemove、mousedown三個事件,分別對應按下滑鼠、滑鼠移動和放下滑鼠三個狀態。在按下滑鼠狀態下,會以當前節點為原型克隆一個節點用於佔位。在拖動滑鼠狀態下,透過改變節點的坐標實現節點位置的改變。在放下滑鼠狀態下,會判斷當前節點是否與其他節點重疊,如果重疊則使重疊節點變為當前節點的父節點,否則,當前節點傳回原來的位置。
其他技術點我就不一一列出來了,有興趣的同學可以到上面的傳送門看看原始碼。