/ Famo.us

[Famo.us] Engine, Context 與 ElementAllocator

[UPDATE] Famo.us 官方已捨棄此模組,詳情請看官方 Github

這三樣東西是組成 Famo.us 引擎的主要功臣,負責 rFA 與 DOM 的溝通橋樑。


Engine 核心

Famo.us 當中,這個核心做的事情其實並不會太多,大抵上就是這些,

  • 設定 rFA
  • 監聽 resize
  • 強迫取消 touchmove 事件(使用 preventDefault()
  • 使用 EventHandler 監聽事件
  • 使用 OptionsManager 設定預設值,並且監聽其 change 來更新元件

Engine 方法

  • pipe, unpipe, on, emit 對於 EventHandler 相關模組的操作
  • getFPS 取得目前 Engine 的 FPS
  • setFPSCap 設定 FPS 上限
  • setOptions 設定 Engine 的屬性
  • createContext 建立一個 div 的 Engine 框架,所有元件都必須被 add 在這個框架裡面。這個框架是採用核心模組 Context 來建立,所以會回傳 Context 這個元件。
  • registerContext 用來註冊一個 Context 元件,並且在下一次 loop 的時候出現在畫面上
  • nextTick 傳入下一個 loop 所需要執行的 function(它只會執行一次
  • defernextTick 很類似,但是是延遲執行(如果每次運行時間小於 MAX_DEFER_FRAME_TIME 的話,預設是 10(單位秒

Engine 監聽事件

  • prerender 在做 nextTickdefer 之前會先 emmit
  • postrender 做完 nextTickdefer 之後才會 emmit

FPS

預設的 Engine 所使用的 FPS 是 1000 / frameTime,而這個 frameTime 的來由,是經過每次 loop 的執行時間去相減(單位是秒,也就是說,

frameTime = 目前時間 - 上一次運行的時間戳記

所以 Engine 並不是真的運行在 60 fps 底下的,而是依照你的瀏覽器執行時間來決定目前的 FPS 該是多少。而,setFPSCap 則是在這個取得 frameTime 的方法之前,做一個限制,

// skip frame if we're over our framerate cap
if (frameTimeLimit && currentTime - lastTime < frameTimeLimit) return;

也就是說,如果你設定 Engine.setFPSCap( 60 ); 就代表這個 frameTimeLimit1000 / 60,如果運行的 loop 小於這個時間(速度過快,那麼這一次的 loop 就不會被執行了。

使用範例

以下是 Engine.createContext, Engine.registerContext 操作範例,

define(function(require, exports, module) {
  var Engine = require('famous/core/Engine');
  var Context = require('famous/core/Context');
  var Surface = require('famous/core/Surface');

  var elem = document.createElement('div');
  elem.style.backgroundColor = 'black';
  elem.style.width = '100px';
  elem.style.height = '100px';
  document.body.appendChild(elem);

  var context = new Context(elem);
  Engine.registerContext( context );

  var mainCtx = Engine.createContext();

  var surface = new Surface({
    size: [100, 100],
    properties: {
      backgroundColor: 'red'
    }
  });

  mainCtx.add(surface);  
});

輸出結果在畫面上,你可以看瀏覽器所產生的 DOM 會有兩個 <div>,大概會長這樣,

<div style="background-color: black; width: 100px; height: 100px;"></div>
<div class="famous-container">
  <div style="transform-origin: 0% 0% 0px; opacity: 0.999999; z-index: 0; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); background-color: black; width: 100px; height: 100px;" class="famous-surface"></div>
</div>

這樣應該可以理解 Engine.createContextEngine.registerContext 的差別。Engine 核心中,最主要的就是 Context 核心處理 DOM 的部分。而處理 DOM 的部分則是 ElementAllocator 來負責,這裡就分別說明。

ElementAllocator 核心

這個核心專門在處理 DOM 中的元素,當你初始化這個物件的時候,預設就會拿到 document.createDocumentFragment() 來當作初始物件(或者是,你也可以傳入一個 DOM 的物件給他

但是,由於這個物件的方法,在官方說明下都是屬於私有方法,單獨要拿出來用也不是不行。只是用法有別。主要這個物件,是用來儲存 DOM 的物件,

  • allocate 用於儲存 DOM 物件
  • deallocate 用於取出儲存的 DOM 物件
  • migrate 把原本所屬的 DOM 子元件,轉移到新的 DOM 父元件底下
  • getNodeCount 計算所屬的子元件數量

底下的例子就會在 document.body 底下加入一個空的 <div> 元素。

  var body = new ElementAllocator(document.getElementsByTagName('body')[0]);
  body.allocate('div');

不過既然他是私有物件,就不再多贅述了。

Context 核心

這個核心主要是將 RenderNode 打包成物件並且將它放到 DOM 裡面。

這個核心包含了,

  • ElementAllocator
  • RenderNode
  • EventHandler

方法

  • add 在這個物件內加入一個 renderable 物件
  • migrateElementAllocator 類似,將這個物件搬移到另一個物件下
  • getSize 取得該物件的寬度與高度
  • setSize 設定該物件的寬度與高度,如果沒有傳入值,就是 document 的尺寸
  • getPerspective 取得該物件的透視深度(單位 px
  • setPerspective 設定物件透視深度,允許三個傳入值,分別是 perspectivetransitioncallback,其中 transition 必須要是 Transitionable 物件
  • on 監聽事件
  • emit 觸發事件
  • removeListener 移除監聽事件
  • pipe, unpipe 將自身事件指定(或取消)到其他物件上

使用方式

這一個元件必須要傳入一個 DOM 的元件,舉個簡單的例子來看,

  var elem = document.createElement('div');
  elem.style.backgroundColor = 'black';
  elem.style.width = '100px';
  elem.style.height = '100px';
  document.body.appendChild(elem);

  var ctx = new Context(elem);

  console.log(ctx);

這個 ctx 物件就可以用來控制 DOM 元件了。