查了一下有道:javascript
Container
n. 集装箱;容器html
我目前用到过的 DisplayObject 有5种:Bitmap, Shape, Text, MovieClip 和 Container。(好像 CreateJS 就只有这5种 DisplayObject)。不过,MovieClip 其实继承自 Container
,因此 MovieClip 能够看成一个 Container。java
与 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 封装起来并使之有了对象(容器与粒子)的概念。面对对象的好处你们都是知道的。
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 />
划分在同一组。从管理的角度上说,划分一组后,能够对一组粒子统一进行如下操做:
透明度
可见性
矩阵转换
什么是矩阵转换?
一个图形的位移(translate)与形变(scale, rotate, skew)能够统一用一个矩阵来表示,因此「矩阵转换」就是位移和形变的统称。原生 Canvas 的 提供了矩阵转换 API:
scale() 缩放当前绘图至更大或更小
rotate() 旋转当前绘图
translate() 从新映射画布上的 (0,0) 位置
transform() 替换绘图的当前转换矩阵
setTransform() 将当前转换重置为单位矩阵。而后运行 transform()
具体能够参见:http://www.w3school.com.cn/ta...
原生 Canvas 存在一个全局矩阵,经过上面的「矩阵转换」API 能够修改这个全局矩阵。「矩阵转换」在使用过程当中有如下两个特色:
「矩阵转换」后不影响已绘制的图像图形,它只做用于以后绘制 API;
「矩阵转换」对全局矩阵的转换是累加性的;
如下代码能够验证上面两个特性:
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 来实现「容器」,我会这样设计:
「容器」是存放子元素(「粒子」与「子容器」)的数组
「粒子」是一个绘制具体任务的 Funtion
子元素由 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的,以下:
https://www.createjs.com/docs...
跟个人设计实际上是相似的。上图红框的 updateContext
其实就是处理「矩阵转换」以下:
https://www.createjs.com/docs...
原本想随手写写的,没想到写得有点长。看成一次记忆加深的过程。