资讯专栏INFORMATION COLUMN

聊聊 Container 的实现

mudiyouyou / 2087人阅读

摘要:原生实现只存在于渲染引擎,原生是不存在的。假设有以下的代码包括了,换种说法是划分在同一组。于是,实现了一群粒子做同一件事其实就是实现了原生下的容器。不过,容器除了嵌套行为,它还有并列的行为兄弟容器。当作一次记忆加深的过程。

查了一下有道:

Container
n. 集装箱;容器

DisplayObject 实例分类

我目前用到过的 DisplayObject 有5种:Bitmap, Shape, Text, MovieClip 和 Container。(好像 CreateJS 就只有这5种 DisplayObject)。不过,MovieClip 其实继承自 Container,所以 MovieClip 可以当作一个 Container。

与 HTML 做个类比:

Bitmap & Shape == ;

Text == 文本;

Container ==

很容易发现除了 Container 能嵌套子元素外,其它 4 种都不能。按这个维度分类,DisplayObject 有两类:

容器:Container

粒子:Bitmap, Shape, Text, MovieClip

「容器」的作用是分组管理「粒子」。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 代码:


    Text...
    
    

包括了 Text... & & ,换种说法是:Text... & & 划分在同一组。从管理的角度上说,划分一组后,可以对一组粒子统一进行以下操作:

透明度

可见性

矩阵转换

什么是矩阵转换?
一个图形的位移(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

来看一下 CreateJS 是怎么实现 Container的,如下:


https://www.createjs.com/docs...

跟我的设计其实是类似的。上图红框的 updateContext 其实就是处理「矩阵转换」如下:


https://www.createjs.com/docs...

结语

本来想随手写写的,没想到写得有点长。当作一次记忆加深的过程。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89641.html

相关文章

  • 聊聊Tomcat架构设计

    摘要:本篇文章主要是跟大家聊聊的内部架构体系,让大家对有个整体的认知。方法会创建一个对象,调用它的方法将字节流封装成对象,在创建组件时,会将组件添加到组件中组件而组件在连接器初始化时就已经创建好了目前为止,只有一个实现类,就是。 微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车! T...

    cnio 评论0 收藏0
  • 简单聊聊浏览器JS事件触发机制

    摘要:事件冒泡由微软提出,事件会从最内从的元素开始发生,再向外传播,正好与事件捕获相反。为了解决上述问题,我们可以利用事件委托的思想,在父级注册一个事件监听器,统一进行子元素的事件处理。 原理 事件捕获 由网景最先提出,事件会从最外层开始发生,直到最具体的元素,也就是说假如父元素与子元素都绑定有点击事件,又互相重叠,那么先出发的会是父元素的事件,然后再传递到子元素。 事件冒泡 由微软提出,事...

    enrecul101 评论0 收藏0

发表评论

0条评论

mudiyouyou

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<