- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > web開(kāi)發(fā) >
- 基于HTML5如何實(shí)現WebGL的3D機房
這篇文章給大家分享的是有關(guān)基于HTML5如何實(shí)現WebGL的3D機房的內容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
效果圖
這個(gè) 3D 機房的 Demo 做的還不錯,比較美觀(guān),基礎的交互也都滿(mǎn)足,接下來(lái)看看怎么實(shí)現。
代碼生成
定義類(lèi)
首先從 index.html 中調用的 js 路徑順序一個(gè)一個(gè)打開(kāi)對應的 js,server.js 中自定義了一個(gè) Editor.Server 類(lèi)由 HT 封裝的 ht.Default.def 函數創(chuàng )建的(注意,創(chuàng )建的類(lèi)名 Editor.Server 前面的 Editor 不能用 E 來(lái)替代):
ht.Default.def('Editor.Server', Object, {//第一個(gè)參數為類(lèi)名,如果為字符串,自動(dòng)注冊到HT的classMap中;第二個(gè)參數為此類(lèi)要繼承的父類(lèi);第三個(gè)參數為方法和變量的聲明 addToDataModel: function(dm) { //將節點(diǎn)添加進(jìn)數據容器 dm.add(this._node);// ht 中的預定義函數,將節點(diǎn)通過(guò) add 方法添加進(jìn)數據容器中 }, setHost: function() { //設置吸附 this._node.setHost.apply(this._node, arguments); }, s3: function() {//設置節點(diǎn)的大小 this._node.s3.apply(this._node, arguments); }, setElevation: function() {//控制Node圖元中心位置所在3D坐標系的y軸位置 this._node.setElevation.apply(this._node, arguments); } });
創(chuàng )建 Editor.Server 類(lèi)
這個(gè)類(lèi)可以創(chuàng )建一個(gè) ht.Node 節點(diǎn),并設置節點(diǎn)的顏色和前面貼圖:
var S = E.Server = function(obj) {//組件 var color = obj.color, frontImg = obj.frontImg; var node = this._node = new ht.Node();//創(chuàng )建節點(diǎn) node.s({//設置節點(diǎn)的樣式 s 為 setStyle 的縮寫(xiě) 'all.color': color,//設置節點(diǎn)六面的顏色 'front.image': frontImg //設置節點(diǎn)正面的圖片 }); };
這樣我在需要創(chuàng )建服務(wù)器組件的位置直接 new 一個(gè)新的服務(wù)器組件對象即可,并且能夠直接調用我們上面聲明的 setHost 等函數,很快我們就會(huì )用上。
接下來(lái)創(chuàng )建 Editor.Cabinet 機柜類(lèi) ,方法跟上面 Editor.Server 類(lèi)的定義方法差不多:
ht.Default.def('Editor.Cabinet', Object, { addToDataModel: function(dm) { dm.add(this._door); dm.add(this._node); this._serverList.forEach(function(s) { s.addToDataModel(dm); }); }, p3: function() { this._node.p3.apply(this._node, arguments);//設置節點(diǎn)的 3d 坐標 } });
創(chuàng )建 Editor.Cabinet 類(lèi)
這個(gè)類(lèi)相對于前面的 Editor.Server 服務(wù)器組件類(lèi)要相對復雜一點(diǎn),這個(gè)類(lèi)中創(chuàng )建了一個(gè)柜身、柜門(mén)以及機柜內部的服務(wù)器組件:
var C = E.Cabinet = function(obj) { var color = obj.color, doorFrontImg = obj.doorFrontImg, doorBackImg = obj.doorBackImg, s3 = obj.s3; var node = this._node = new ht.Node(); // 柜身 node.s3(s3);//設置節點(diǎn)的大小 為 setSize3d node.a('cabinet', this);//自定義 cabinet 屬性 node.s({//設置節點(diǎn)的樣式 為 setStyle 'all.color': color,//設置節點(diǎn)六面的顏色 'front.visible': false//設置節點(diǎn)前面是否可見(jiàn) }); if (Math.random() > 0.5) { node.addStyleIcon('alarm', {//向節點(diǎn)上添加 icon 圖標 names: ['icon 溫度計'],//包含多個(gè)字符串的數組,每個(gè)字符串對應一張圖片或矢量(通過(guò)ht.Default.setImage注冊) face: 'top',//默認值為front,圖標在3D下的朝向,可取值left|right|top|bottom|front|back|center position: 17,//指定icons的位置 autorotate: 'y',//默認值為false,圖標在3D下是否自動(dòng)朝向眼睛的方向 t3: [0, 16, 0],//默認值為undefined,圖標在3D下的偏移,格式為[x,y,z] width: 37,//指定每個(gè)icon的寬度,默認根據注冊圖片時(shí)的寬度 height: 32,//指定每個(gè)icon的高度,默認根據注冊圖片時(shí)的高度 textureScale: 4,//默認值為2,該值代表內存實(shí)際生成貼圖的倍數,不宜設置過(guò)大否則影響性能 visible: { func: function() { return !!E.alarmVisible; }}//表示該組圖片是否顯示 }); } var door = this._door = new ht.DoorWindow();//柜門(mén) door.setWidth(s3[0]);//置圖元在3D拓撲中的x軸方向的長(cháng)度 door.setHeight(1);//設置圖元在3D拓撲中的z軸長(cháng)度 door.setTall(s3[1]);//控制Node圖元在y軸的長(cháng)度 door.setElevation(0);//設置圖元中心在3D坐標系中的y坐標 door.setY(s3[2] * 0.5);//設置節點(diǎn)在 y 軸的位置 door.setHost(node);//設置吸附 door.s({//設置節點(diǎn)樣式 setStyle 'all.color': color,//設置節點(diǎn)六面顏色 'front.image': doorFrontImg,//設置節點(diǎn)正面圖片 'front.transparent': true,//設置節點(diǎn)正面是否透明 'back.image': doorBackImg,//設置節點(diǎn)背面的圖片 'back.uv': [1,0, 1,1, 0,1, 0,0],//自定義節點(diǎn)后面uv貼圖,為空采用默認值[0,0, 0,1, 1,1, 1,0] 'dw.axis': 'right'//設置DoorWindow圖元展開(kāi)和關(guān)閉操作的旋轉軸,可取值left|right|top|bottom|v|h }); var serverList = this._serverList = []; var max = 6, list = E.randomList(max, Math.floor(Math.random() * (max - 2)) + 2); //global.js 中聲明的獲取隨機數的函數 var server, h = s3[0] / 4; list.forEach(function(r) { var server = new E.Server({ //服務(wù)器組件 color: 'rgb(51,49,49)', frontImg: '服務(wù)器 組件精細' }); server.s3(s3[0] - 2, h, s3[2] - 4);//設置節點(diǎn)大小 server.setElevation((r - max * 0.5) * (h + 2));//設置節點(diǎn)中心點(diǎn)在 y 軸的坐標 server.setHost(node);//設置節點(diǎn)的吸附 serverList.push(server);//向 serverList 中添加 server 節點(diǎn) }); };
上面代碼中唯一沒(méi)提到的是 Editor.randomList 函數,這個(gè)函數是在 global.js 文件中聲明的,聲明如下:
var E = window.Editor = { leftWidth: 0, topHeight: 40, randomList: function(max, size) { var list = [], ran; while (list.length < size) { ran = Math.floor(Math.random() * max); if (list.indexOf(ran) >= 0) continue; list.push(ran); } return list; } };
好了,場(chǎng)景中的各個(gè)部分的類(lèi)都創(chuàng )建完成,那我們就該將場(chǎng)景創(chuàng )建起來(lái),然后將這些圖元都堆進(jìn)去!
場(chǎng)景創(chuàng )建
如果熟悉的同學(xué)應該知道,用 HT 創(chuàng )建一個(gè) 3D 場(chǎng)景只需要 new 一個(gè) 3D 組件,再將通過(guò) addToDOM 函數將這個(gè)場(chǎng)景添加進(jìn) body 中即可:
var g3d = E.main = new ht.graph4d.Graph4dView(); //3d 場(chǎng)景
main.js 文件中主要做的是在 3D 場(chǎng)景中一些必要的元素,比如墻面,地板,門(mén),空調以及所有的機柜的生成和排放位置,還有非常重要的交互部分。
墻體,地板,門(mén),空調和機柜的創(chuàng )建我就不貼代碼出來(lái)了,有興趣的請自行查看代碼,這里主要說(shuō)一下雙擊機柜以及與機柜有關(guān)的任何物體(柜門(mén),服務(wù)器設備)則 3D 中 camera 的視線(xiàn)就會(huì )移動(dòng)到雙擊的機柜的前方某個(gè)位置,而且這個(gè)移動(dòng)是非常順滑的,之前技藝不精,導致這個(gè)部分想了很久,最后參考了這個(gè) Demo 的實(shí)現方法。
為了能夠重復地設置 eye 和 center,將設置這兩個(gè)參數對應的內容封裝為 setEye 和 setCenter 方法,setCenter 方法與 setEye 方法類(lèi)似,這里不重復贅述:
// 設置眼睛位置 var setEye = function(eye, finish) { if (!eye) return; var e = g3d.getEye().slice(0),//獲取當前 eye 的值 dx = eye[0] - e[0], dy = eye[1] - e[1], dz = eye[2] - e[2]; // 啟動(dòng) 500 毫秒的動(dòng)畫(huà)過(guò)度 ht.Default.startAnim({ duration: 500, easing: easing,//動(dòng)畫(huà)緩動(dòng)函數 finishFunc: finish || function() {}, //動(dòng)畫(huà)結束后調用的函數 action: function(v, t) {//設置動(dòng)畫(huà)v代表通過(guò)easing(t)函數運算后的值,t代表當前動(dòng)畫(huà)進(jìn)行的進(jìn)度[0~1],一般屬性變化根據v參數進(jìn)行 g3d.setEye([ //設置 3D 場(chǎng)景中的 eye 眼睛的值,為一個(gè)數組,分別對應 x,y,z 軸的值 e[0] + dx * v, e[1] + dy * v, e[2] + dz * v ]); } }); };
我沒(méi)有重復聲明 setCenter 函數不代表這個(gè)函數不重要,恰恰相反,這個(gè)函數在“視線(xiàn)”移動(dòng)的過(guò)程中起到了決定性的作用,上面的 setEye 函數相當于我想走到我的目標位置的前面(至少我定義的時(shí)候是這種用途),而 sCenter 的定義則是將我的視線(xiàn)移到了目標的位置(比如我可以站在我現在的位置看我右后方的物體,也可以走到我右后方去,站在那個(gè)物體前面看它),這點(diǎn)非常重要,請大家好好品味一下。
雙擊事件倒是簡(jiǎn)單,只要監聽(tīng) HT 封裝好的事件,判斷事件類(lèi)型,并作出相應的動(dòng)作即可:
g3d.mi(function(e) {//addInteractorListener 事件監聽(tīng)函數 if (e.kind !== 'doubleClickData') //判斷事件類(lèi)型為雙擊節點(diǎn) return; var data = e.data, p3; if (data.a('cabinet')) //機身 p3 = data.p3(); else { host = data.getHost(); //獲取點(diǎn)擊節點(diǎn)的吸附對象 if (host && host.a('cabinet')) {//如果吸附對象為 cabinet p3 = host.p3(); } } if (!p3) return; setCenter(p3); //設置 center 目標的要移向位置為 cabinet 的位置 setEye([p3[0], 211, p3[2] + 247]); //設置 eye 眼睛要移向的位置 });
頂部導航欄
一開(kāi)始看到這個(gè)例子的時(shí)候我在想,這人好厲害,我用 HT 這么久,用 HT 的 ht.widget.Toolbar 還沒(méi)能做出這么漂亮的效果,看著(zhù)看著(zhù)發(fā)現這原來(lái)是用 form 表單做的,厲害厲害,我真是太愚鈍了。
var form = E.top = new ht.widget.FormPane(); //頂部 表單組件 form.setRowHeight(E.topHeight);//設置行高 form.setVGap(-E.topHeight);//設置表單組件水平間距 設置為行高的負值則可以使多行處于同一行 form.setVPadding(0);//設置表單頂部和頂部與組件內容的間距 form.addRow([null, {//向表單中添加一行組件,第一個(gè)參數為元素數組,元素可為字符串、json格式描述的組件參數信息、html元素或者為null image: { icon: './symbols/inputBG.json', stretch: 'centerUniform' } }], [40, 260]);//第二個(gè)參數為每個(gè)元素寬度信息數組,寬度值大于1代表固定絕對值,小于等于1代表相對值,也可為80+0.3的組合 form.addRow([null, null, { id: 'searchInput', textField: {} }, { element: '機房可視化管理系統', color: 'white', font: '18px arial, sans-serif' }, null, { button: { // label: '視圖切換', icon: './symbols/viewChange.json', background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function() { E.focusTo(); } } }, null, { button: { // label: '告警', icon: './symbols/alarm.json', togglable: true, selected: false, background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function(e) { E.setAlarmVisible(this.isSelected()); } } }, null], [40, 42, 218, 300, 0.1, 50, 10, 50, 10]);
以上都只是能實(shí)現,但是并沒(méi)有真正地添加進(jìn) html 標簽中,也就意味著(zhù),現在界面上什么都沒(méi)有!別忘了在頁(yè)面加載的時(shí)候將 3D 場(chǎng)景添加進(jìn) body 中,同時(shí)也別忘了將 form 表單添加進(jìn) body 中,并且設置窗口大小變化事件時(shí),form 表單也需要實(shí)時(shí)更新:
window.addEventListener('load', function() { g3d.addToDOM(); //將 3D 場(chǎng)景添加進(jìn) body 中 document.body.appendChild(E.top.getView()); //將 form 表單組件底層 div 添加進(jìn) body 中 window.addEventListener('resize', function() {//窗口大小變化事件監聽(tīng) E.top.iv();//更新 form 表單的底層 div }); });
這里說(shuō)明一下 addToDOM 函數,對于了解 HT 的機制非常重要。HT 的組件一般都會(huì )嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的 HT 組件則需要用戶(hù)手工將 getView() 返回的底層 div 元素添加到頁(yè)面的 DOM 元素中,這里需要注意的是,當父容器大小變化時(shí),如果父容器是 BorderPane 和 SplitView 等這些 HT 預定義的容器組件,則 HT 的容器會(huì )自動(dòng)遞歸調用孩子組件invalidate 函數通知更新。但如果父容器是原生的 html 元素, 則 HT 組件無(wú)法獲知需要更新,因此最外層的 HT 組件一般需要監聽(tīng) window 的窗口大小變化事件,調用最外層組件 invalidate 函數進(jìn)行更新。
為了最外層組件加載填充滿(mǎn)窗口的方便性,HT 的所有組件都有 addToDOM 函數,其實(shí)現邏輯如下,其中 iv 是 invalidate 的簡(jiǎn)寫(xiě):
addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); //將場(chǎng)景的底層 div 添加進(jìn) body 中 style.left = '0';//HT 默認將所有的組件底層div的position設置為absolute style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //窗口大小變化監聽(tīng)事件,通知組件變化更新 }
這樣,所有的代碼就結束了,可以自己右鍵“檢查”,network 中可以獲取相對應的 json 文件。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系站長(cháng)郵箱:ts@56dr.com進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2 建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站