隧道的專案我目前是第一次接觸,感覺做起來的效果還蠻讚的,所以給大家分享一下。這個隧道專案的主要內容包括:照明、風機、車道指示燈、交通訊號燈、情報板、消防、火災報警、車行橫洞、風向儀、COVI、微波車檢以及隧道緊急逃生出口的控制。這個例子我主要講一下風向儀、排風以及逃生出口的動畫設定, 還有就是點選交通訊號燈,彈出視窗可以選擇當前訊號燈的資訊等內容。
本例效果:
我是用 HT 做的整個例子,首先建立 3D 場景,HT 有 3D 元件,可以直接通過 new ht.graph3d.Graph3dView 3D 元件來建立一個例項,然後通過 getView() 函式獲取元件的底層 div,既然是 div,那位置顯示控制就容易得多了:
dm = new ht.DataModel();//資料容器,可以將顯示在介面上的所有資料通過 dataModel.add 儲存在資料容器中 g3d = new ht.graph3d.Graph3dView(dm);//3D 元件 g3d.addToDOM();//將 3D 元件的底層 div 新增到 body 中
HT 的元件一般都會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的 HT 元件則需要使用者手工將 getView() 返回的底層 div元素新增到頁面的 DOM 元素中,這裡需要注意的是,當父容器大小變化時,如果父容器是 BorderPane 和 SplitView 等這些HT
預定義的容器元件,則HT
的容器會自動遞迴呼叫孩子元件 invalidate 函式通知更新。但如果父容器是原生的 html 元素, 則HT
元件無法獲知需要更新,因此最外層的 HT 元件一般需要監聽 window 的視窗大小變化事件,呼叫最外層元件 invalidate 函式進行更新。
為了最外層元件載入填充滿視窗的方便性,HT 的所有元件都有 addToDOM 函式,其實現邏輯如下,其中 iv 是 invalidate 的簡寫:
addToDOM = function(){ var self = this, view = self.getView(), //獲取元件的底層 div style = view.style; document.body.appendChild(view); //將元件的底層 div 新增進 body 中 style.left = '0';//HT 元件預設設定 position 樣式屬性為 absolute 絕對定位方式 style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); }
最讓我開心的應該是我的開發基本上跟設計部分完全分離了,因為 HT 可以通過 ht.Default.xhrLoad 函式直接載入 json 檔案的場景,這樣我跟設計師就是雙程式了,非常開心呢~載入場景有三個步驟,如下:
ht.Default.xhrLoad('scenes/隧道.json', function(text){//載入 json 場景 var json = ht.Default.parse(text);//轉義 json 檔案 dm.deserialize(json);//將 json 內容反序列化到場景中 //可以在這個裡面任意操作 datamodel 資料容器中的資料了 }
我在場景中新增了一些功能,包括前面提到過的一些動畫操作,HT 封裝好的 dataModel.addScheduleTask(task) 通過運算元據容器 dataModel 來控制載入動畫,動畫部分在引數 task 中宣告,task 為 json 物件,可指定如下屬性:
- interval:間隔毫秒數,預設值為 10
- enabled:是否啟用開關,預設為 true
- action:間隔動作函式,該函式必須設定
我的動畫一共三個,兩個隧道中各有一個風扇、一個風向儀以及一個卷閘門。設定這三個圖元變化即可,我在 json 中分別將這三個圖元的 tag 設定為 feng、feng2 以及 door,在程式碼中我就可以直接呼叫這三個圖元的 tag 屬性:
var task = { action: function(data){ if(!data.getTag()) return; var tag = data.getTag();//獲取圖元的 tag 屬性 if(tag === 'feng'){ data.r3(0, (data.r3()[1]+Math.PI/12), 0);//r3 為 3d 中的旋轉,這裡 y 軸在原來的基礎上再旋轉 Math.PI/12 角度 }else if(tag === 'feng2'){ data.r3(0, 0, data.r3()[2]+Math.PI/12); }else if(tag === 'door'){ if(data.getTall() > 0){//獲取圖元的 tall 屬性,高度 data.setTall(data.getTall()-20);//設定高度為當前高度減去20 } } } } dm.addScheduleTask(task);//在資料容器 dataModel 中新增排程任務
接著是建立 form 表單,在表單上新增一些資訊,比如交通燈的切換等等,場景預設顯示的右上角的 form 表單我們這裡不做解釋,內容跟點選交通燈出現的 form 表單差不多,所以我們主要說明一下點選交通燈時出現的表單:
表單中重複的部分比較多,我挑出三個部分來解釋一下:文字部分、“當前狀態”顯示的圖示以及下面“修改狀態”中的圖示點選選擇部分:
form.addRow([//addRow 新增一行 我這個部分是新增一個標題 { element: '交通燈控制',//這一行第一部分的顯示文字 align: 'center',//文字對齊方式 color: 'rgb(0,210,187)',//文字顏色 font: 'bold 16px arial, sans-serif'//文字字型 } ], [0.1]);//記得要設定這行的寬度 form.addRow([ //這行中有兩個部分,一個“裝置描述”,一個 文字“0”,所以要設定兩個寬度,寬度要放在一個陣列中 '裝置描述:',//第一部分 {//第二部分 element: '0', color: 'rgb(0,210,187)' } ],[80, 0.1], 34);//addRow 函式第二個引數為寬度設定,將上面內容的寬度依次放進這個陣列中。第三個引數為高度 form.addRow([ '當前狀態:', {//也可以將陣列中的某個部分設定為空字串,佔據一些寬度,這樣比例比較好調 element: '' }, { id: '105',//id唯一標示屬性,可通過formPane.getItemById(id)獲取新增到對應的item物件 button: {//按鈕,設定了該屬性後HT將根據屬性值自動構建ht.widget.Button物件,並儲存在element屬性上 icon: 'symbols/隧道用圖示/light.json',//按鈕上的顯示圖示 background: 'rgba(0,7,26,0.60)',//按鈕背景 borderColor: 'rgb(0, 7, 26)',//按鈕邊框顏色 clickable: false//是否可點選 } } ],[80, 0.1, 84], 30); form.addRow([//如果和上面一行的距離差別與其它行間距不同,可以通過增加一行空行,設定高度即可 '', { element: '' } ], [200, 0.1], 10); form.addRow([ '修改狀態:', { element: '' }, { button: { icon: 'symbols/隧道用圖示/light.json',//設定按鈕的圖示 background: 'rgba(0,7,26,0.60)', borderColor: 'rgb(0, 7, 26)', groupId: 'btn',//通過getGroupId和setGroupId獲取和設定組編號,屬於同組的togglable按鈕具有互斥功能。後面的三個按鈕也是設定了同一個 groupId onClicked: function(e){//點選後的回撥函式 btnClick('light'); } } } ],[80, 0.1, 84], 30);
這個 form 表單的背景只是設定了一張圖片而已:
background: url('assets/控制.png') no-repeat;
上面還有一個部分沒有提及,就是點選按鈕後呼叫的 btnClick 函式:
function btnClick(imageName){ if(flag === 1){//做的判斷是根據3d的事件來處理的,等下會提 dm.getDataByTag('light').s({//通過getDataByTag獲取節點,設定節點的style樣式 'back.image': 'symbols/隧道用圖示/'+imageName+'.json',//設定圖元的背面圖片 'front.image': 'symbols/隧道用圖示/'+imageName+'.json'//設定圖元你的前面圖片 }); }else if(flag === 2){ dm.getDataByTag('light1').s({ 'back.image': 'symbols/隧道用圖示/'+imageName+'.json', 'front.image': 'symbols/隧道用圖示/'+imageName+'.json' }); }else{} form.getViewById(105).setIcon('symbols/隧道用圖示/'+imageName+'.json');//設定id為105的item內容顯示的圖示為form表單上點選的交通燈的按鈕的圖示 }
最後就是點選事件了,一個是點選 3D 中的交通燈後出現交通燈控制的 form 表單,還有一個就是點選 form 表單上的“修改狀態”中的圖示事件:
g3d.mi(function(e){//addInteractorListener 函式 監聽場景中的事件 form.getView().style.display = 'none';//所有的點選圖元事件都先將form隱藏 if(e.kind === 'clickData'){//點選圖元 if(e.data.getTag() === 'light'){//如果圖元是正面的隧道的燈 form.getView().style.display = 'block';//顯示form表單 form.iv();//重新整理form表單,不然介面無法獲知此時需要重新整理form顯示上面的內容,必須手動操作 flag = 1; }else if(e.data.getTag() === 'light1'){//背面的隧道的燈,點選切換隧道中的燈的顯示,另外一個隧道中的燈不可能一起改變,所以要區分開 form.getView().style.display = 'block'; form.iv(); flag = 2; }else if(e.data.getTag() === 'wall'){//隧道牆體 e.data.s({ 'shape3d.transparent': true,//開啟設定透明的開關 'shape3d.opacity': 0.2,//設定透明度 '3d.selectable': false//點選後就不可選中隧道了 }); } }else if(e.kind === 'doubleClickBackground'){//點選空白處 設定隧道牆體不透明 dm.getDatas().forEach(function(data){//遍歷dataModel if(data.getTag() === 'wall'){ data.s({ 'shape3d.transparent': false,//關閉設定透明的開關,這樣可以不用控制透明度 '3d.selectable': true//雙擊空白處可以使得隧道變得可選中 }); } }); } });