之前在拓撲上的應用都是些靜態的圖元,今天我們將在拓撲上設計一個會動的圖元——葉輪旋轉。
先看看最後我們實現的效果:http://www.hightopo.com/demo/fan/index.html
我們先來看下這個葉輪模型長什麼樣
從模型上看,這個葉輪模型有三個葉片,每一個葉片都是不規則圖形,顯然無法用上我們HT for Web的基礎圖形來拼接,那麼我們該怎麼做呢?很簡單,在HT for Web中提供了自定義圖形的方案,我們可以通過自定義圖形來繪製像葉片這種不規則圖形。
在繪製葉片之前,我們得先來了解下HT for Web的自定義圖形繪製的基本知識:
繪製自定義圖形需要制定向量型別為shape,並通過points的Array陣列指定每個點資訊, points以[x1, y1, x2, y2, x3, y3, ...]的方式儲存點座標。曲線的多邊形可通過segments的Array陣列來描述, segment以[1, 2, 1, 3 ...]的方式描述每個線段:
1: moveTo,佔用1個點資訊,代表一個新路徑的起點
2: lineTo,佔用1個點資訊,代表從上次最後點連線到該點
3: quadraticCurveTo,佔用2個點資訊,第一個點作為曲線控制點,第二個點作為曲線結束點
4: bezierCurveTo,佔用3個點資訊,第一和第二個點作為曲線控制點,第三個點作為曲線結束點
5: closePath,不佔用點資訊,代表本次路徑繪製結束,並閉合到路徑的起始點
對比閉合多邊形除了設定segments引數外,還可以設定closePath屬性: * closePath獲取和設定多邊形是否閉合,預設為false,對閉合直線採用這種方式,無需設定segments引數。
好了,那麼接下來我們開始設計葉片了
ht.Default.setImage('vane', { width: 97, height: 106, comps: [ { type: 'shape', points: [ 92, 67, 62, 7, 0, 70, 60, 98 ], segments: [ 1, 2, 2, 2 ], background : 'red' } ] });
我們在向量中定義了4個頂點,並且將這4個頂點通過直線勾勒出葉片的大致形狀,雖然有些抽象,但是,接下來將會通過增加控制點和改變segment引數來讓這個葉片發生蛻變。
首先我們通過bezierCurveTo方式向第一個和第二個頂點之間的線段新增兩個控制點,從而繪製出曲線,以下是points及segments屬性:
points: [ 92, 67, 93, 35, 78, 0, 62, 7, 0, 70, 60, 98 ], segments: [ 1, 4, 2, 2 ]
這時候與上一個圖相比較,有一條邊一件有些弧度了,那麼接下來就來處理第二條邊和第三條邊
points: [ 92, 67, 93, 35, 78, 0, 62, 7, 29, 13, 4, 46, 0, 70, 28, 53, 68, 60, 60, 98 ], segments: [ 1, 4, 4, 4 ]
看吧,現在是不是有模有樣了,現在葉片已經有了,那麼接下來要做的就是使用三個這樣的葉片拼接成一個葉輪。
將已有的資源拼接在一起需要用到向量中的image型別類定義新的向量,具體的使用方法如下:
ht.Default.setImage('impeller', { width: 166, height: 180.666, comps : [ { type: 'image', name: 'vane', rect: [0, 0, 97, 106] }, { type: 'image', name: 'vane', rect: [87.45, 26.95, 97, 106], rotation: 2 * Math.PI / 3 }, { type: 'image', name: 'vane', rect: [20.45, 89.2, 97, 106], rotation: 2 * Math.PI / 3 * 2 } ] });
在程式碼中,我們定義了三個葉片,並且對第二個和第三個葉片做了旋轉和定位的處理,讓這三個葉片排布組合成一個葉輪來,但是怎麼能讓葉輪中間空出一個三角形呢,這個問題解決起來不難,我們只需要在葉片的points屬性上再多加一個頂點,就可以填充這個三角形了,程式碼如下:
points: [ 92, 67, 93, 35, 78, 0, 62, 7, 29, 13, 4, 46, 0, 70, 28, 53, 68, 60, 60, 98, 97, 106 ], segments: [ 1, 4, 4, 4, 2 ]
在points屬性上新增了一個頂點後,別忘了在segments陣列的最後面新增一個描述,再來看看最終的效果:
到這個葉輪的資源就做好了,那麼接下來就是要讓這個葉輪旋轉起來了,我們先來分析下:
要讓葉輪旋轉起來,其實原理很簡單,我們只需要設定rotation屬性就可以實現了,但是這個rotation屬性只有在不斷的變化中,才會讓葉輪旋轉起來,所以這個時候就需要用到定時器了,通過定時器來不斷地設定rotation屬性,讓葉輪動起來。
恩,好像就是這樣子的,那麼我們來實現一下:
首先是建立一個節點,並設定其引用的image為impeller,再將其新增到DataModel,令節點在拓撲中顯示出來:
var node = new ht.Node(); node.setSize(166, 181); node.setPosition(400, 400); node.setImage('impeller'); dataModel.add(node);
接下來就是新增一個定時器了:
window.setInterval(function() { var rotation = node.getRotation() + Math.PI / 10; if (rotation > Math.PI * 2) { rotation -= Math.PI * 2; } node.setRotation(rotation); }, 40);
OK了,好像就是這個效果,但是當你選中這個節點的時候,你會發現這個節點的邊框在不停的閃動,看起來並不是那麼的舒服,為什麼會出現這種情況呢?原因很簡單,當設定了節點的rotation屬性後,節點的顯示區域就會發生變化,這個時候節點的寬高自然就發生的變化,其邊框也自然跟著改變。
還有,在很多情況下,節點的rotation屬性及寬高屬性會被當成業務屬性來處理,不太適合被實時改變,那麼我們該如何處理,才能在不不改變節點的rotation屬性的前提下令葉輪轉動起來呢?
在向量中,好像有資料繫結的功能,在手冊中是這麼介紹的:
繫結的格式很簡單,只需將以前的引數值用一個帶func屬性的物件替換即可,func的內容有以下幾種型別:
1. function型別,直接呼叫該函式,並傳入相關Data和view物件,由函式返回值決定引數值,即func(data, view);呼叫。
2. string型別:
2.1 style@***開頭,則返回data.getStyle(***)值,其中***代表style的屬性名。
2.2 attr@***開頭,則返回data.getAttr(***)值,其中***代表attr的屬性名。
2.3 field@***開頭,則返回data.***值,其中***代表data的屬性名。
2.4 如果不匹配以上情況,則直接將string型別作為data物件的函式名呼叫data.***(view),返回值作為引數值。
除了func屬性外,還可設定value屬性作為預設值,如果對應的func取得的值為undefined或null時,則會採用value屬性定義的預設值。 例如以下程式碼,如果對應的Data物件的attr屬性stateColor為undefined或null時,則會採用yellow顏色:
color: { func: 'attr@stateColor', value: 'yellow' }
資料繫結的用法已經介紹得很清楚了,我們不妨先試試繫結葉片的背景色吧,看下好不好使。在向量vane中的background屬性設定成資料繫結的形式,程式碼如下:
background : { value : 'red', func : 'attr@vane_background' }
在沒有設定vane_background屬性的時候,令其去red為預設值,那麼接下來我們來定義下vane_background屬性為blue,看看葉輪會不會變成藍色:
node.setAttr('vane_background', ‘blue');
看下效果:
果然生效了,這下好了,我們就可以讓葉輪旋轉變得更加完美了,來看看具體該這麼做。
首先,我們先在節點上定義一個自定義屬性,名字為:impeller_rotation
node.setAttr('impeller_rotation', 0);
然後再定義一個名字為rotate_impeller的向量,並將rotation屬性繫結到節點的impeller_rotation上:
ht.Default.setImage('rotate_impeller', { width : 220, height : 220, comps : [ { type : 'image', name : 'impeller', rect : [27, 20, 166, 180.666], rotation : { func : function(data) { return data.getAttr('impeller_rotation'); } } } ] });
這時候我們在定時器中修改節點的rotation屬性改成修改自定義屬性impeller_rotation就可以讓節點中的葉輪旋轉起來,並且不會影響到節點自身的屬性,這就是我們想要的效果。
在2D上可以實現,在3D上一樣可以實現,下一章我們就來講講葉輪旋轉在3D上的應用,今天就先到這裡,下面附上今天Demo的原始碼,有什麼問題歡迎大家諮詢。
http://www.hightopo.com/demo/fan/index.html
ht.Default.setImage('vane', { width : 97, height : 106, comps : [ { type : 'shape', points : [ 92, 67, 93, 35, 78, 0, 62, 7, 29, 13, 4, 46, 0, 70, 28, 53, 68, 60, 60, 98, 97, 106 ], segments : [ 1, 4, 4, 4, 2 ], background : { value : 'red', func : 'attr@vane_background' } } ] }); ht.Default.setImage('impeller', { width : 166, height : 180.666, comps : [ { type : 'image', name : 'vane', rect : [0, 0, 97, 106] }, { type : 'image', name : 'vane', rect : [87.45, 26.95, 97, 106], rotation : 2 * Math.PI / 3 }, { type : 'image', name : 'vane', rect : [20.45, 89.2, 97, 106], rotation : 2 * Math.PI / 3 * 2 } ] }); ht.Default.setImage('rotate_impeller', { width : 220, height : 220, comps : [ { type : 'image', name : 'impeller', rect : [27, 20, 166, 180.666], rotation : { func : function(data) { return data.getAttr('impeller_rotation'); } } } ] }); function init() { var dataModel = new ht.DataModel(); var graphView = new ht.graph.GraphView(dataModel); var view = graphView.getView(); view.className = "view"; document.body.appendChild(view); var node = new ht.Node(); node.setSize(220, 220); node.setPosition(200, 400); node.setImage('rotate_impeller'); node.setAttr('impeller_rotation', 0); node.setAttr('vane_background', 'blue'); dataModel.add(node); var node1 = new ht.Node(); node1.setSize(166, 181); node1.setPosition(500, 400); node1.setImage('impeller'); dataModel.add(node1); window.setInterval(function() { var rotation = node.a('impeller_rotation') + Math.PI / 10; if (rotation > Math.PI * 2) { rotation -= Math.PI * 2; } node.a('impeller_rotation', rotation); node1.setRotation(rotation); }, 40); }