聊聊 Container 的實現

查了一下有道:javascript

Container
n. 集裝箱;容器html

DisplayObject 實例分類

我目前用到過的 DisplayObject 有5種:Bitmap, Shape, Text, MovieClip 和 Container。(好像 CreateJS 就只有這5種 DisplayObject)。不過,MovieClip 其實繼承自 Container,因此 MovieClip 能夠看成一個 Container。java

clipboard.png

與 HTML 作個類比:canvas

  • Bitmap & Shape == <img />;數組

  • Text == 文本;spa

  • Container == <div></div>設計

很容易發現除了 Container 能嵌套子元素外,其它 4 種都不能。按這個維度分類,DisplayObject 有兩類:rest

  • 容器:Containercode

  • 粒子:Bitmap, Shape, Text, MovieCliporm

「容器」的做用是分組管理「粒子」。CreateJS 的 Stage 就是最著名的一個容器。

But...原生 Canvas 並無粒子與容器的概念,它有繪製圖片圖形等底層APIs。相似於 CreateJS 的 Canvas 渲染引擎的厲害之處就在於把底層 APIs 封裝起來並使之有了對象(容器與粒子)的概念。面對對象的好處你們都是知道的。

原生 APIs 實現 Container

Container 只存在於渲染引擎,原生 Canvas 是不存在的。「粒子」也不存在於原生 Canvas,可是若是把 MovieClip 剔除,其它三個「粒子」其實都有對應的原生 Canvas APIs:

  • Bitmap ------ drawImage

  • Shape ------ rect/arc/moveTo/lineTo...

  • Text ---- fillText

因爲有一一對應的 API,因此粒子在實現就是一個一一對應搬 APIs 的過程。可是「容器」就須要討論一下了。

假設有如下的 HTML 代碼:

<container>
    Text...
    <bitmap />
    <shape />
</container>

<container></container> 包括了 Text... & <bitmap /> & <shape />,換種說法是:Text... & <bitmap /> & <shape /> 劃分在同一組。從管理的角度上說,劃分一組後,能夠對一組粒子統一進行如下操做:

  1. 透明度

  2. 可見性

  3. 矩陣轉換

什麼是矩陣轉換?
一個圖形的位移(translate)與形變(scale, rotate, skew)能夠統一用一個矩陣來表示,因此「矩陣轉換」就是位移和形變的統稱。原生 Canvas 的 提供了矩陣轉換 API:

  • scale() 縮放當前繪圖至更大或更小

  • rotate() 旋轉當前繪圖

  • translate() 從新映射畫布上的 (0,0) 位置

  • transform() 替換繪圖的當前轉換矩陣

  • setTransform() 將當前轉換重置爲單位矩陣。而後運行 transform()

具體能夠參見:http://www.w3school.com.cn/ta...

原生 Canvas 存在一個全局矩陣,經過上面的「矩陣轉換」API 能夠修改這個全局矩陣。「矩陣轉換」在使用過程當中有如下兩個特色:

  1. 「矩陣轉換」後不影響已繪製的圖像圖形,它只做用於以後繪製 API;

  2. 「矩陣轉換」對全局矩陣的轉換是累加性的;

如下代碼能夠驗證上面兩個特性:

var ctx = canvas.getContext("2d"); 
ctx.rect(150, 150, 400, 400); 
ctx.fillStyle = "#d00000"; 
ctx.fill(); 
// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(150, 150, 400, 400); 
ctx.fillStyle = "#00d000"; 
ctx.fill(); 

// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(150, 150, 400, 400); 
ctx.fillStyle = "#0000d0"; 
ctx.fill();

截圖以下:

圖片描述

分「容器」後的「粒子」能夠統一作同一件事,那麼統一作同一件事的一羣「粒子」不就能夠認爲是一個「容器」。因而,實現了一羣「粒子」作同一件事其實就是實現了原生 Canvas 下的「容器」。

「矩陣轉換」的第一個特性像極了「容器」的行爲,第二個特性像極了「容器」嵌套「容器」的行爲。不過,「容器」除了嵌套行爲,它還有並列的行爲(兄弟容器)。

如何實現「容器」並列?
兄弟「容器」A 與 B,A 比 B 早出如今畫布上;做爲兄弟「容器」,A 的「矩陣轉換」不能對 B 產生影響,這好像跟第二個特性衝突了!!!!不過,能累加的東西就有辦法能夠反向累加,以下:

var ctx = canvas.getContext("2d");
// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(150, 150, 400, 400); 
ctx.fillStyle = "#00d000"; 
ctx.fill(); 

// 反向累加 ----- 消除上次的轉換
ctx.scale(2, 2); 

// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(600, 150, 400, 400); 
ctx.fillStyle = "#0000d0"; 
ctx.fill();

效果截圖以下:

圖片描述

理論上經過反向累加能夠實現矩陣回退的效果,但在嵌套複雜的狀況下,這個方案比較複雜而麻煩。But...別忘了 Canvas 有一對兄弟 APIs: save & restore。

  • save() 保存當前環境的狀態

  • restore() 回退到上一次 save 保存的狀態

經過這兩個 APIs 能夠輕鬆地實現「全局矩陣」的回退,從而達到實現「兄弟容器」目的,以下:

var ctx = canvas.getContext("2d");

// 保存狀態
ctx.save(); 
// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(150, 150, 400, 400); 
ctx.fillStyle = "#00d000"; 
ctx.fill(); 

// 恢復到上一次狀態
ctx.restore(); 

// 縮小一倍
ctx.beginPath(); 
ctx.scale(.5, .5); 
ctx.rect(600, 150, 400, 400); 
ctx.fillStyle = "#0000d0"; 
ctx.fill();

截圖與使用反向累加同樣。

若是讓我用原生 canvas 來實現「容器」,我會這樣設計:

  1. 「容器」是存放子元素(「粒子」與「子容器」)的數組

  2. 「粒子」是一個繪製具體任務的 Funtion

  3. 子元素由 ctx.save() 開始,ctx.restore()結束

如下是僞代碼:

var ctx = canvas.getContext("2d");
let [containerA, containerB, stage] = [[], [], [containerA, containerB]]; 
// 粒子是一個繪製圖形的 function
let particleA1 = () => {
    ctx.rect(x, y, w, h); 
    ctx.fillStyle = "#d00000"; 
    ctx.fill();  
}
let particleA2 = () => ...; 
let particleB1 = () => ...; 
let particleB2 = () => ...; 

containerA = [particleA1, particleA2]; 
containerB = [particleB1, particleB2]; 

// 繪製 container
let renderContainer = container => container.forEach(
    child => {
        // 保存狀態
        ctx.save(); 
        // 子元素是容器
        if(isContainer(child)) renderContainer(child); 
        // 粒子
        else renderParticle(child);
        // 恢復狀態
        ctx.restore(); 
    }
); 

// 吐出 stage 
renderContainer(stage);

CreateJS 的 Container

來看一下 CreateJS 是怎麼實現 Container的,以下:

圖片描述
https://www.createjs.com/docs...

跟個人設計實際上是相似的。上圖紅框的 updateContext 其實就是處理「矩陣轉換」以下:

圖片描述
https://www.createjs.com/docs...

結語

原本想隨手寫寫的,沒想到寫得有點長。看成一次記憶加深的過程。

相關文章
相關標籤/搜索