canvas 動畫庫 CreateJs 之 EaselJS(下篇)


本文來自網易雲社區

作者:田亞楠


繼承

對應原文:Inheritance

我們可以繼承已有的「顯示對象」,創建新的自定義類。實現方法有很多種,下面介紹其中之一。

舉例:實現一個繼承於 Container 類的自定義類 Button:

共分 4 步:

  1. 自定義構造器

  2. 繼承父類,獲得父類的功能

  3. 重寫已有方法,擴展自身方法

  4. promote 繼承來的方法,返回自定義類 

(function () {    // 自定義構造器
    //  - 調用繼承的父類構造器()
    //  - label 為自定義入參
    var Button = function (label) {      this.Container_constructor();      this.label = label;
    };    // 繼承 Container 類
    //  - 將 Container 添加到 Button 的調用鏈上,賦予 Button 所有 Container 的方法
    //  - 返回值為 Button.prototype
    var p = createjs.extend(Button, createjs.Container);    // 重寫 draw 方法
    //  - 首先調用父類的 draw 方法
    p.draw = function () {      this.Container_draw();      // 添加自定義邏輯
    };    // 將父類 Container 的所有方法(包括 constructor)都重寫添加到 Button
    //  - 例如上文用到的:Container_constructor、Container_draw
    //  - 新方法命名規則:prefix_methodName,prefix 為參數 "Container"
    window.Button = createjs.promote(Button, "Container");
  })();

createjs.promote 跟 createjs.extend 是不同的。上面代碼的邏輯是:

  1. 首先通過 extend,賦予 Button 所有 Container 的方法(只是放在調用鏈 Button.prototype 上);

  2. 再通過 promote,將調用鏈上的方法 提升(promote) 到 Button 對象上,並修改了方法名,防止命名沖突,便於記憶。

原文中的 DEMO源碼


命中測試

對應原文:HITTEST


hitTest & globalToLocal

顯示對象擁有函數 hitTest ,用來檢測一個點是否在該顯示對象中間。我們可以通過它來檢測指針(鼠標/手指點按)是否在一個圖形中。

circle.hitTest(stage.mouseX, stage.mouseY);

上面的寫法檢測指針當前的位置(stage.mouseX, stage.mouseY)是否在 circle 圓形中,但存在一個問題。

無論 createjs.Stage 類,還是 createjs.Shape 類,都繼承於 createjs.Container 類。 他們都是一個容器,都擁有 addChild 方法,因此可以互相嵌套。並且他們都位於「各自的坐標系」中。 其中 stage 的坐標系我們可以稱為 global(世界坐標系),而其他的容器對象如 cirlce 的坐標系我們稱為 local。

circle.hitTest 的參數當然需要基於 circle 的坐標系,而 stage.mouseX 獲得的點的坐標系是基於 global 的。

因此我們需要通過 circle.globalToLocal(stage.mouseX, stage.mouseY)(返回值為:{x, y} 對象), 來將 global 坐標系的坐標轉換為 circle 坐標系的坐標。

參考:DEMODEMO2

從 DEMO2 可以看出 globalToLocal 方法會將目標對象的父元素的「圖形變換」的因素都考慮在內。 如果有多級元素嵌套,我們仍可以使用該方法將 global 坐標轉換為內層子元素的 local 坐標。


localToLocal

除了檢測指針相對於圖形的位置,我們還可以檢測一個顯示對象相對於另一個顯示對象的位置。

objA.localToLocal(posX, posY, objB) 方法,可以將 objA 對象的 local 坐標系中的點(posX, posY), 映射到 objB 對象的 local 坐標系中。返回值仍然為 {x:, y:}。

注意:(posX, posY)坐標是從 objA 映射到 objB,source.localToLocal(x, y, target)    

參考:DEMO


鼠標交互

對應原文:Mouse Interaction

基礎

鼠標交互,就是監聽鼠標/手指等的交互事件。「顯示對象」通過使用 addEventListener 即可監聽事件。比如 click 事件:

circle.addEventListener('click', function () { alert('circle clicked') });

可以監聽的事件有:
click, dblclick,
mousedown, pressmove, pressup,
mouseover / mouseout, and rollover / rollout.

其中最后 4 個(mouseover / mouseout, rollover / rollout)是有一定關聯的,它們默認不啟用,使用的時候需要:

stage.enableMouseOver(frequency);

其中 frequency 是指在一秒內檢查(計算)多少次事件是否觸發。默認值為 20 次/秒。 設置的值越高,相應速度越快,但相應的需要更多的計算量。

這樣做的好處是使 檢查的頻率 與 設置的幀率 解耦。

有幾點需要注意:

  1. 沒有 mouseup 和 pressdown 事件。可以把 mousedown、pressmove、pressup 分成一組。

  2. pressup 與 click 事件的區別是,click 事件在同一點按下與抬起時觸發,而 pressup 會在任意一處拿起時都會觸發。


  3. on 方法可以用來替代 addEventListener,並且 on 方法還額外提供了一些參數:


circle.on(type, listener, scope, once, data, useCapture);

監聽事件的回調函數 listener 的參數是一個 EaselJS 定義的 MouseEvent 對象,它包含一些有用的屬性:

  1. type:事件類型('mousedown'、'pressmove'、'pressup' 等)

  2. target:觸發事件的顯示對象

  3. nativeEvent:基於的原生事件對象

  4. stageX、stageY:觸發事件的點在 global 坐標系的坐標

  5. 還有一些不常用的屬性,可參考 API

參考:DEMO

通過上面的 DEMO,當多個事件同時觸發時(更換綁定順序結果不變):

  1. 先觸發 click 再觸發 pressup

  2. 先 rollover 再 mouseover

  3. 先 mouseout 再 rollout


對性能的好處

事件 mouseover / mouseout, rollover / rollout 也可以通過對檢查頻率的設置,來優化性能。 要知道小於 100ms 的響應時間用戶是幾乎不會感知到的,而它只需要 10fps,相對於 60fps 的動畫來說性能提升了 6 倍。

而其他的事件 click, dblclick, mousedown, pressmove, pressup, 我們可以通過監聽原生事件,在「對應的原生事件」觸發的時候才調用回調,而不是放到 tick 循環中,因此可以提升性能。


事件冒泡

對於 DOM 節點來說,當一個事件被觸發之后,會經過 3 個階段:捕獲階段、目標階段、冒泡階段

參考文章:事件階段MDN 文章中有一個很直觀的demo

補充:當事件進行到目標階段時,目標階段上注冊的捕獲事件和冒泡事件的觸發順序是由注冊順序決定的(addEventListener 代碼的執行順序)

注冊捕獲事件需要使用 addEventListener(type, listener, useCapture) 的第三個參數 useCapture 設置為 true,

跟在 DOM 上綁定事件一樣,createjs 也對事件的觸發有着相似的處理方法。

由於是對虛擬的 js 對象(而非 DOM 結構)進行事件的綁定,因此它內部的處理方式是 createjs 仿照 DOM 的機制實現的一套邏輯。 跟 DOM 事件沒有必然聯系。其本質上都是 canvas 元素觸發了事件之后,再由 createjs 進行處理。

createjs 中的 on 方法也有 useCapture 參數用來注冊捕獲事件:circle.on(type, listener, scope, once, data, useCapture)

我們先明確一下名詞的定義(以 click 事件為例):

  1. target:觸發事件的節點中最內層的節點。比如點擊有多個節點重合(父節點子節點都有),那么最內層的子節點就是 target。

  2. currentTarget:事件流轉到的當前節點。

createjs 中的對象/容器處理事件也經過 3 個同樣的階段:

  1. 捕獲階段:
    首先觸發 stage 的捕獲事件(stage 上綁定的 useCapture == true 的事件),然后依次觸發 target 的最外層祖先容器到最內層父容器的捕獲事件

  2. 目標階段:
    target 對象觸發自身的事件(包括所有捕獲事件和冒泡事件)

  3. 冒泡階段:
    與捕獲階段相反,依次觸發 target 的最內層父容器到最外層祖先容器,直到 stage 對象的冒泡事件(useCapture == false 默認值)

這個 DEMO 中所有容器與顯示對象都注冊了 click 事件(包括 useCapture 值為 true 和 false 兩種), 其中 button 對象是一個 Container 容器對象,它包含兩個顯示對象:background、label。你可以通過點擊「紅色背景」和「白色文字」來分別查看對應「事件階段」結果。

另外還有兩個屬性用來控制捕獲&冒泡:

  1. mouseChildren 可用來將一個顯示對象集合作為一個事件整體,如上面的 DEMO 中,設置 button.mouseChildren = false; 那么 button 這個容器 所包含的所有子顯示對象的事件將不會觸發,整個 button 集合將作為整體對事件進行相應。

  2. mouseEnabled 顧名思義,可以用來禁止一個對象的所有事件。需要注意的是,如果 button 這個集合設置了 button.mouseEnabled = false; 那么它的所有子顯示對象的事件將都不會再被觸發了。


HITAREA

前文介紹了鼠標交互的各種事件,但可以被觸發事件的只有顯示對象的「可見 且 不透明 」的像素點。 在上面事件階段的 DEMO 中,可以發現 label (按鈕的文字)上注冊的事件想要被觸發, 必須精確的點擊到「文字的線條」上。

createjs 提供了 hitArea 。你可以設置另一個對象 objB 作為顯示對象 objA 的 hitArea,當點擊到 objB 時就相當於點擊到了 objA。 這個 objB 不需要添加到顯示對象列表,也不需要可見,但它會在交互事件的觸發中替代 objA。

注意:hitTest 命中檢測並不適用於 hitArea,命中檢測還是針對顯示對象的「可見且不透明」的像素點(不然命中檢測的邏輯就顯得混亂了)。 hitArea 只針對交互事件的觸發。如果真的有這種需求,可以非常簡單的自己實現。

參考:DEMO

上面的 demo 中,container 對象為所有藍色的圓形的整體,它的 hitArea 是紅色的圓形, 當指針 mouseover 紅色的圓形時,container 的 mouseover 事件會被觸發。


stage 對象的交互事件

在 EaselJS 0.5 版本之前,stage 對象是無法綁定交互事件的,后來有人提了 ISSUES,在之后的版本中解決掉了這個問題。

一般的顯示對象監聽事件觸發的范圍為「可見且不透明」的像素點,而 stage 對象顯然不同。

stage 對象有它特殊的交互事件:stagemousedown, stagemouseup, stagemousemove,整個 canvas 都對它們生效。

注:stage 對象還可以監聽 click、mouseleave、mouseenter,后兩者可以監聽指針進入/離開 canvas。

當指針離開 canvas 范圍之后 stagemousemove 事件就不會觸發了,如果希望在畫布之外繼續觸發事件,需要設置: stage.mouseMoveOutside = true; 。 之后當離開畫布范圍后,stage 的 3 個特殊事件都會繼續被監聽。

evt.stageX, evt.stageY 不會超出畫布的邊界范圍(大於 0 小於 width/height),如果希望獲取到外界的坐標,可以使用 evt.rawX, evt.rawY。

//超出畫布之后仍允許監聽 stage 的各種事件stage.mouseMoveOutside = true;

stage.on("stagemousemove", function(evt) {    //永遠在邊界以內
    console.log("stageX/Y: "+evt.stageX+","+evt.stageY);    //可以超出邊界,小於 0 或 大於 canvas 的寬高(CW/CH)
    console.log("rawX/Y: "+evt.rawX+","+evt.rawY);
});

我們可以通過 stage.mouseInBounds (參考之前 hitTest 的這個 DEMO 有用到)來判斷指針是否離開 canvas 范圍。 或者監聽 stage 的事件:mouseleave, mouseenter 來判斷。

參考:DEMO


拖拽

通過對前面介紹過的一些事件的監聽,我們可以非常方便的實現拖拽效果。主要是使用 mousedown, pressmove, pressup 這一組事件。

直接看 DEMO


其它常用 API

  1. Container.getObjectUnderPoint(x, y, mode) ,參考 API doc

  2. Container.getObjectsUnderPoint(x, y[, mode = 0]) ,參考 API doc

  3. DisplayObject.hitTest(x, y),本文前面的「命中測試」部分介紹過

  4. ...請閱讀 API doc


相關閱讀: canvas 動畫庫 CreateJs 之 EaselJS(上篇)

網易雲免費體驗館,0成本體驗20+款雲產品! 

更多網易研發、產品、運營經驗分享請訪問網易雲社區



相關文章:
【推薦】 Kubernetes在網易雲中的落地優化實踐


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM