基於HTML5 Canvas 點選新增 2D 3D 機櫃模型

圖撲軟體發表於2017-11-27

今天又返回好好地消化了一下我們的資料容器 DataModel,這裡給新手做一個典型的資料模型事件處理的例子作為參考。這個例子看起來很簡單,實際上結合了資料模型中非常重要的三個事件處理的部分:屬性變化事件監聽、選中變化事件監聽以及資料模型變化事件監聽。

為了讓這個例子具現化,我將這個簡單的例子做了一點改動,下面我會一一解釋。

例子地址:http://hightopo.com/guide/guide/core/datamodel/examples/example_datamodel.html

 

這是我改造之後的模樣,將 dataModel 資料容器共享,通過對資料容器的增刪事件的監聽得到的現在的結果,並且在顯示上做了一點“手腳”。下面我們從頭解析這例子,你們會知道為什麼我特地將這個簡單的例子提出來。

首先,我們得建立場景將作為基礎,整個場景我算是分為三個部分,頂部工具欄,2D 部分以及 3D 部分。頂部工具欄部分使用的純 HTML 寫的:

<button onclick="addData()">Add</button>
<button onclick="removeData()">Remove</button>
<button onclick="clearDataModel()">Clear</button>
<span id="property" class="output"></span>
<span id="model" class="output"></span>
<span id="selection" class="output"></span>

因為有點選事件,所以我們直接在 button 按鈕上進行,後面的 span 標籤顯示純文字內容。

我們知道,HT 的所有元件都是基於一個根部 div 的,要將這個 div 部署到 html 頁面上很簡單,但是 HT 內部對這個 div 設定了絕對定位,所以我們在新增這個 div 進 HTML 頁面中時,也要設定絕對定位中的位置,我在頁面中新增了一個 div,將 HT 的部分都新增進這個 div 中:

<div id="myDiv" style="border: 1px solid red; width: 800px; height: 600px;position: absolute; "></div>
dataModel = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dataModel);
g3d.setGridVisible(true);//設定網格可見
g3d.setEye(185, 50, 470);//設定3d的眼睛位置
g3d.setCenter(200, 47, 10);//設定3d的中心位置, 這兩個屬性都是為了讓使用者看3d上的場景更舒服,更直接
g2d = new ht.graph.GraphView(dataModel);
g2d.setEditable(true);//設定2d圖元可編輯
g2d.fitContent(true);//將所有的圖元顯示到頁面上
splitView = new ht.widget.SplitView(g2d, g3d, 'v', 0.3);//分割元件,裝了2d和3d兩個場景
splitView.addToDOM();//將分割元件新增進body中,並設定絕對定位的位置
myDiv = document.getElementById('myDiv');
myDiv.appendChild(splitView.getView());//將分割元件新增進myDiv中

接著新增節點進入 dataModel 資料模型之中,我們這裡做的是機房的機櫃,本來想做的是伺服器,手頭上暫時只有這個資源,也不賴。我封裝了一個增加函式,一個刪除函式,還有一個清楚函式,分別對應的是工具欄上的“Add”、“Remove”以及“Clear”三個功能:

function addData() {
    var data = new ht.Node();
    data.setPosition(index*60, 50);
    data.setName('node'+index);
    data.setSize(40, 40);
    data.setImage('cabinet');
    data.s({
        'image.stretch': 'centerUniform',
    'shape3d': 'cabinet'
    });
    index++;
    dataModel.add(data);
    return data;
}
function removeData() {
    if(!dataModel.sm().ld()) return;
    dataModel.remove(dataModel.sm().ld());
}
function clearDataModel(){
    dataModel.clear();
    index = 0;
}

其中,程式碼中出現的“data.setImage('cabinet')”,是我通過 ht.Default.setImage('cabinet', 'imageURL') 方式定義的,呼叫的時候直接 data.setImage('imageName') 即可,具體參考 HT for Web 入門手冊 image 章節。

2D 的圖片顯示肯定和 3D 的模型顯示是不一樣的,2D 中我們直接用貼圖就能解決,而 HT 3D 中支援 obj 格式的模型顯示,就是這個部分:

 

HT 封裝瞭解析 obj 格式的函式 ht.Default.loadObj 函式用來匯入模型,該函式有三個引數,第一第二分別為 obj 檔案的路徑和 mtl 檔案的路徑,第三個引數為 json 格式控制引數,具體引數請參考 HT for Web OBJ 手冊 loadObj 函式章節(ps:用 obj 模型會導致跨域問題,要放到伺服器上執行):

ht.Default.loadObj('obj/機櫃元件1.obj', 'obj/機櫃元件1.mtl', {
     cube: true,//是否將模型縮放到單位1的尺寸範圍內,預設為false
     center: true,//模型是否居中
     prefix: 'obj/',//路徑字首,如果前面引數寫了路徑字首,這個不寫也可以
     shape3d: 'cabinet',//指定 shape3d 名稱
     finishFunc: function(modelMap, array, rawS3){//呼叫ht.Default.parseObj解析後的返回值,若載入或解析失敗則返回值為空
         window.rawS3 = rawS3;//讓當前模型的尺寸為原始尺寸
         if(modelMap){
             cabinet1 = addData();//新增兩個節點到 dataModel 中
             cabinet2 = addData();
         }
    } 
});

現在,節點和模型都已經匯入到場景中了,終於來到了我們今天的重點,事件互動部分。ht.DataModel 資料容器管理著 Data 資料的增刪以及變化事件的派發,這裡我們就這兩種事件進行對 Data 資料的管理。

1. addDataModelChangeListener(function(e) {}, scope) 增加資料模型增刪變化事件監聽器,可用簡寫 mm(func, scope), func 為監聽器函式,scope 為監聽器函式域(可選),在監聽器函式中的 event 有兩個屬性: kind 和 data,其中 kind 為事件的型別:

  • e.kind === 'add'代表新增Data物件,e.data為被新增的物件
  • e.kind === 'remove'代表刪除Data物件,e.data為被刪除的物件
  • e.kind === 'clear'代表容器被清除

這裡我們將對模型的增刪事件的監聽結果傳給 HTML 中的 id 為 model 的 span 作為內容:

var model = document.getElementById('model');
dataModel.addDataModelChangeListener(function(e) {
    if(e.kind === 'add')//如果事件型別為 add 增加節點
        model.innerHTML = e.data + ' added';//就將model 的內容替換為 新增的節點 added
    if(e.kind === 'remove')
        model.innerHTML = e.data + ' removed';
    if(e.kind === 'clear')
        model.innerHTML = 'dataModel cleared'
});

2. addDataPropertyChangeListener(function(e) {}, scope) 增加模型中 Data 資料屬性變化事件監聽器,可用簡寫 md(func, scope),其中 event 事件有四種屬性:

  • e.data代表屬性變化的物件
  • e.property代表變化屬性的名字
  • e.newValue代表屬性的新值
  • e.oldValue代表屬性的老值
  • Data物件在設定屬性值函式內呼叫firePropertyChange(property, oldValue, newValue)觸發屬性變化事件:
    • get/set型別屬性,如setAge(98)觸發事件的e.propertyage
    • style型別屬性名前加s:字首以區分,如setStyle('age', 98)觸發事件的e.propertys:age
    • attr型別屬性名前加a:字首以區分,如setAttr('age', 98)觸發事件的e.propertya:age

這裡我們將對模型中 Data 的屬性變化事件的監聽結果傳給 HTML 中的 id 為 property 的 span 作為內容:

var model = document.getElementById('model');
dataModel.addDataPropertyChangeListener(function(e) {
    property.innerHTML = e.data + '\'s ' + e.property + ' has changed, the old value is ' + e.oldValue + ' and the new value is ' + e.newValue; 
});

3. 最後,我們對選中的節點進行增加監聽器,監聽選中變化事件。ht.SelectionModel管理 DataModel 模型中 Data 物件的選擇狀態, 每個 DataModel 物件都內建一個 SelectionModel 選擇模型,控制這個 SelectionModel 即可控制所有繫結該 DataModel 的元件的物件選擇狀態, 這意味著共享同一 DataModel 的元件預設就具有選中聯動功能。

如果希望某些元件不與其他元件選中聯動,可通過呼叫 view.setSelectionModelShared(false), 這樣該 view 將建立一個專屬的 SelectionModel 例項。

綜上所述有兩種途徑可得到 SelectionModel:

  • dataModel.getSelectionModel()獲取資料容器中元件共享的選中模型。
  • view.getSelectionModel()獲取當前元件使用的選中模型,selectionModelSharedfalse時,返回view專用的選擇模型。

addSelectionChangeListener(function(e) {}, scope)增加監聽器,監聽選中變化事件,簡寫為 ms(func, scope):

  • e.datas包含所有選中狀態變化的物件,之前選中現在取消選中,或之前沒選中現在被選中的物件
  • e.kind === 'set'代表此事件由setSelection(datas)引發
  • e.kind === 'remove'代表此事件由removeSelection(datas)引發
  • e.kind === 'append'代表此事件由appendSelection(datas)引發
  • e.kind === 'clear'代表此事件由clearSelection(datas)引發

這裡我們將對模型中 Data 的選中變化事件的監聽結果傳給 HTML 中的 id 為 selection 的 span 作為內容:

var selection = document.getElementById('selection');
dataModel.sm().addSelectionChangeListener(function(e){
    if(dataModel.sm().size() === 0) selection.innerHTML = 'Nothing selected';//如果選中模型的“長度”為0,即沒有選中內容
    else if(dataModel.sm().size() === 1) selection.innerHTML = e.datas + ' selected';
    else selection.innerHTML = dataModel.sm().size() + ' datas selected';
});

以上,所有的程式碼全部分析完畢!大家可以天馬行空,建立出屬於你自己的3維模型!

 

相關文章