前言
在前端中,檢視層和資料層需要進行單向或者雙向資料繫結,大家都已經不陌生了,有時候 2D 做的比較順了之後,就會想要挑戰一下 3D,不然總覺得癢癢的。這個 3D 機架的 Demo 我覺得非常有代表性,首先,3D 機架用途非常廣,尤其是在電信行業,就算不是機架,在比如工業方面 3D 模型以及資料繫結的應用也是非常廣泛的,畢竟現在工業物聯網已經是大趨勢了。
效果圖
上面動圖中,閃爍燈是在不斷變化的,由於需要顯示的效果美觀一點,也實際一點,我截的圖還是比較完整的,但是這個閃爍的部分有點看不清楚(cnblog 中放太明顯的外鏈容易被移出首頁啊!!!等會再發!)。
程式碼實現
雖然上面 gif 圖中顯示的一個是 2D 的一個是 3D 的,但是構建的步驟以及需要的內容是一樣的,所以本文只針對 3D 的模型進行程式碼實現。
場景搭建
搭建一個 3D 場景是非常快速的,只需要三行程式碼:
dm = new ht.DataModel();//建立一個資料容器 資料容器也可以通過 g3d.getDataModel() 獲取 g3d = new ht.graph3d.Graph3dView(dm);//建立一個 3D 場景,將資料容器作為引數傳遞進去,這樣資料容器中的內容就可以顯示在 3D 場景中了 g3d.addToDOM();//將 3D 場景新增到 body 體中
3D 機架模型構建
雖然可以叫設計師直接給我一個 obj 格式的模型,但是我覺得這個比較簡答,還是不要麻煩人家了。。。首先是建立一個六面體,模型上面的貼圖是我以前用的一個 json 格式的檔案,用來作為這個六面體的正面貼圖,這些部分都是寫在 json 檔案裡面的,我先擷取一小部分的 json 內容,然後用 js 程式碼復現:
{ "c": "ht.Node", //一個 ht.Node 型別的元素 "i": 1277,//id "p": {//通過 set/get 來設定/獲取的元素的部分。如 setPosition/getPosition "tag": "service",//設定元素標籤 用來作為唯一標識 "image": "symbols/機櫃.json",//設定節點圖片 "rotationX": 1.5708,//設定節點 X 軸旋轉角度 "position": {//設定節點位置 "x": 0, "y": 225 }, "anchor": {//設定節點錨點 "x": 0.5, "y": 0.54 }, "anchorElevation": 1, //設定節點 y 軸錨點 "width": 507,//設定節點寬度 "height": 980, //設定節點長度 "tall": 450, //設定節點高度 "elevation": 451 //控制Node圖元中心位置所在3D座標系的y軸位置 }, "s": {//設定圖元的 style 樣式,HT 預定義的一些樣式屬性,通過 node.s('all.color') 獲取和設定節點的樣式 "all.color": "#DDDDDD", //設定節點六面顏色 "top.image": "symbols/機櫃.json",//設定節點頂部圖片 "front.visible": true, //設定節點正面是否可見 "back.visible": true, "left.visible": true, "right.visible": true, "bottom.visible": true } }
這部分的 json 內容大體上就是建立了一個 ht.Node 節點,然後對這個節點設定了一些屬性,包括節點座標,節點的大小,以及一些 style 樣式設定。
那麼,如何用程式碼來建立這樣一個節點呢?
var node = new ht.Node();//建立一個 ht.Node 型別的節點 node.setTag('service'); //設定節點的標籤 node.setImage('symbols/機櫃.json'); //設定節點圖片 node.setRotationX(Math.PI/2);//設定節點x軸旋轉 node.setPosition(0, 225);//設定節點位置 node.setAnchor(0.5, 0.54);//設定節點錨點 node.setAnchorElevation(1);//設定節點y軸方向的錨點 node.setWidth(507);//設定節點的寬度 node.setHeight(980);//設定節點的長度 node.Tall(450);//設定節點的高度 node.setElevation(451);//控制Node圖元中心位置所在3D座標系的y軸位置 node.s({ //設定節點樣式 'all.color': '#ddd', //六面顏色 'top.image': 'symbols/機櫃.json', //節點頂部圖片 'front.visible': true,//設定節點正面可見 'back.visible': true, 'left.visible': true, 'right.visible': true, 'bottom.visible': true });
其實整個 json 就是由多個這種型別的圖元組合而成的。我們來拆析一下,整個 3D 機架實際上是由十個圖元組合而成的,第一個是整體的 3D 機櫃(也就是我們上面 json 內容中建立的部分),剩下的九個都是需要動態變化閃爍燈的裝置,也就是我紅框框起來的部分:
這些裝置的建立方式跟上面的 3D 機架是類似的,只不過對應的節點尺寸小點,貼圖不一樣,座標不一樣罷了。但是下面的這九個節點的貼圖似乎有點不一樣?上面有閃爍的燈,並且不止一盞!怎麼動態獲取他們呢?
向量--資料繫結
不得不說到向量這個概念。向量在 HT for Web 中是向量圖形的簡稱,常見的 png 和 jpg 這類的柵格點陣圖, 通過儲存每個畫素的顏色資訊來描述圖形,這種方式的圖片在拉伸放大或縮小時會出現圖形模糊,線條變粗出現鋸齒等問題。 而向量圖片通過點、線和多邊形來描述圖形,因此在無限放大和縮小圖片的情況下依然能保持一致的精確度。
這些有點都是次要的,最重要的是這個向量可以進行資料繫結(這個資料繫結是繫結到節點中的),而且繫結方式非常容易!
向量採用 json 格式描述,使用方式和普通的柵格點陣圖一致,通過 node.setImage('hightopo.json') 或者 node.setIcon('hightopo.json') 等設定到資料模型中。
向量 json 描述必需包含 width、height 和 comps 引數資訊:
- width 向量圖形的寬度
- height 向量圖形的高度
- comps 向量圖形的元件 Array 陣列,每個陣列物件為一個獨立的元件型別,陣列的順序為元件繪製先後順序
由於這張圖繪製的還是比較複雜的,所以我就將設定了資料繫結的矩形部分的向量繪製程式碼貼上出來:
{ "width": 48, //一個向量圖示必備的寬度 向量詳細內容請參考 HT for Web 向量手冊 "height": 262,//一個向量圖示必備的高度 "comps": [//一個向量圖示必備的 Array 陣列元件 {//陣列元件中的第一個元素 "type": "rect",//型別為矩形 "background": {//設定矩形背景色 "func": "attr@rectBg1", //HT 用一個帶func屬性的物件替換以前的引數值 這裡就是進行資料繫結的地方 "value": "rgb(255,0,0)"//如果 func 值為 undefined 或者 null 時,採用這個值 }, "shadow": true,//設定“陰影” "shadowColor": { //“陰影”顏色 "func": "attr@shadowColor1", // 這邊將“陰影”也進行了資料繫結,為的是能夠實現燈“發光”的效果 "value": "rgba(255,0,0,0.35)"//設定備選值 }, "shadowOffsetX": 0, //設定陰影橫軸偏移量 "shadowOffsetY": 0,//設定陰影縱軸偏移量 "rect": [//設定該元件的寬高以及座標 4.38544,//x 軸座標 23.52679,//y 軸座標 14.46481, //width 元件寬度 6.1554//height 元件高度 ] } ] }
因為一個向量圖形中有 5 個“閃爍燈”,所以我新增了 5 個元件,也就是在 comps 引數裡面新增了五個元素,繫結的資料不同,為了省事,我將繫結的資料名都設定為“rectBg”後面加一個數字,這些數字依次遞增。
我們就是將這樣一張向量圖設定為節點的 front.image 作為節點正面顯示圖的:node.s('front.image', 'symbols/內部裝置2.json')。
JSON 反序列化
會不會有人好奇 json 檔案裡面的內容是如何轉換成 3D 模型的?
說實在的,步驟依然很簡單:
ht.Default.xhrLoad('scene/service3d.json', function(text) {//xhrLoad 函式是一個非同步載入檔案的函式 dm.deserialize(text);//反序列化資料容器,解析用於生成對應的Data物件並新增到資料容器 這裡相當於把 json 檔案中生成的 ht.Node 節點反序列化到資料容器中,這樣資料容器中就有這個節點了 });
由於 xhrLoad 函式是一個非同步載入函式,所以如果 dm 資料容器反序列化未完成就直接呼叫了其中的節點,那麼會造成資料獲取不到的結果,所以一般來說我是將一些邏輯程式碼寫在這個函式內部,或者給邏輯程式碼設定 timeout 錯開時間差。
首先,由於資料都是儲存在 dm 資料容器中的(通過 dm.add(node) 新增的),所以我們要獲取資料除了可以通過 id、tag 等獨立的方式,還可以通過遍歷資料容器來獲取多個元素:
var infos = [ { shadowColor: 'shadowColor1', background: 'rectBg1' }, { shadowColor: 'shadowColor2', background: 'rectBg2' }, { shadowColor: 'shadowColor3', background: 'rectBg3' }, { shadowColor: 'shadowColor4', background: 'rectBg4' }, { shadowColor: 'shadowColor5', background: 'rectBg5' } ]; var len = infos.length; //獲取陣列中的長度 var datas = dm.toDatas(function(d) { return d.getDisplayName() === 'device'; });//以過濾函式構建新的元素集合並返回(ht.List 陣列型別) setInterval(function() { datas.forEach(function(data) { var info = infos[Math.floor(Math.random() * len)]; var shadowName = info.shadowColor; var bgName = info.background; if (data.a(shadowName) === 'rgba(255, 0, 0, 0.35)') {//若節點業務屬性“陰影”顏色為紅色,則設定為綠色 data.a(shadowName, 'rgba(0, 255, 0, 0.35)'); } else {//若節點業務屬性“陰影”顏色為綠色,則設定為紅色 data.a(shadowName, 'rgba(255, 0, 0, 0.35)'); } if (data.a(bgName) === 'rgb(255, 0, 0)') {//若節點業務屬性背景顏色為紅色,則設定為綠色 data.a(bgName, 'rgb(0, 255, 0)'); } else {//若節點業務屬性背景顏色為綠色,則設定為紅色 data.a(bgName, 'rgb(255, 0, 0)'); } }); }, 1000);
結束語
整體來說這個 Demo 還是比較有價值的,一是快速實現了 3D 機櫃模型,二是對模型上的元素進行了資料繫結。只是想讓你們知道,清晰的圖片繪製沒有那麼難~ 3D 的世界沒有那麼難~ 資料繫結也沒有那麼難!我希望也能讓您發現這並不是件難事。