如上图所示,首先浏览器把Html、CSS解析成Dom Tree和Style Rules,并把两者做关联,这样我们便可以很方便的通过JS去操作他们。
但Dom Tree中有太多的东西并不是显示相关的,比如事件的监听、创建删除操作等,为了方便浏览器绘图,将Dom中显示相关的属性做提取,并对一些布局相关的属性(float、inline)做计算,最后形成一棵Render Tree,Render Tree描述了每个节点的位置、大小、深度等绘图需要的基础信息,有了这些信息,接下来的绘图工作就方便了很多。
使用Chrome控制台的TimeLine,我们可以很清楚的看到这些流程
顺便说一下:
现在用的不多,主要是因为GPU的普及,不过还是很有必要介绍下的。
再看下图一,一个渲染的过程有很多的步骤,如果都由一个线程完成,很难保证不卡顿,于是浏览器将图片解码、绘图的操作从主线程移到了其他线程。
如果绘图出现卡顿,不会影响主线程的业务逻辑,只会出现显示上的跳帧。
此时我们已经有一个Render Tree了,接下来按照Render Layer 的层次,在一个绘图上下文(可以形象的理解成一块画布),先画底层元素,再画上层元素,最终将所有元素绘制在一张位图中。
渲染好后,通知浏览器进程,浏览器进程调用系统的GDI接口将图像输出到最终的显示设备上。
Shared memory中的缓存的好处:当界面有滚动条滚动时,Shared memory中的缓存不需要重绘,只需要重新发送数据给显示设备即可。对比的做法是,直接操作显示设备的缓存,这样可能会出现闪屏和额外的绘图开销。这种两个缓存的技术也被成为双缓冲。
当界面元素变动时,这个时候我们就要对界面进行重新绘制。
方案一:所有元素重新画一遍
方案二:绘制变动的部分
对比后,明显感到方案二的代价更小,相当于用橡皮擦掉一块,再重新画。
界面有变动时,先将变动的元素所影响的区域标记出来,然后再按顺序绘制和此区域相交的所有元素。
打开Chrome控制台,开启Paint flashing,我们就能看到这样的重绘区域。
为了更加深入的了解这个绘制过程,我用Canvas 2D来演示下,大家可以拷贝这段代码到本地来执行。
打开控制台的Console面板,开启Paint flashing的显示,先后运行s1();s2();s3();s4();并观察重绘区域。在s3中绘制了一整个红色背景,但实际的重绘区域只有很小的一部分,s2的代码是关键。
顺便说一下:(国产的白鹭引擎脏矩形绘制也是这样来实现的)
<!DOCTYPE html>
<html>
<body>
<div>红色的背景上,蓝色的方块,从左侧移动到右侧</div>
<br/>
<div style="width:300px;height:150px;">
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3">
Your browser does not support the HTML5 canvas tag.
</canvas>
</div>
<script> var c=document.getElementById("myCanvas"); var ctx=c.getContext("2d"); ctx.fillStyle="red"; ctx.fillRect(0,0,300,150); ctx.fillStyle="blue"; ctx.fillRect(40,40,40,40); function s1(){ console.log("扣除脏区"); ctx.clearRect(40,40,40,40); ctx.clearRect(150,40,40,40); } function s2(){ console.log("设置重绘区域") ctx.rect(40,40,40,40); ctx.rect(150,40,40,40); ctx.clip(); } function s3(){ console.log("绘制红色背景"); ctx.fillStyle="red"; ctx.fillRect(0,0,300,150); } function s4(){ console.log("绘制蓝色方块"); ctx.fillStyle="blue"; ctx.fillRect(150,40,40,40); } </script>
</body>
</html>
Canvas的绘制和Dom的绘制还是有些区别的,同样的功能用Dom实现如下,这时候再来观察脏区域,是不是比Canvas少了些。Dom实现代码如下:
<!DOCTYPE html>
<html>
<body>
<div>红色的背景上,蓝色的方块,从左侧移动到右侧</div>
<br/>
<div style="position:relative;width:300px;height:150px;background:red">
<div id="c" style="width:40px;height:40px;left:40px;top:40px;position:absolute;background:blue;"></div>
</div>
<script> function s(){ var c=document.getElementById("c"); c.style.left="150px"; } </script>
</body>
</html>
接着脏矩形继续讲,如果和脏区域重叠的元素过多怎么办,除了一个个的重新绘制我们还有其他的更好的办法么。
答案是有,想想我们前面讲的,我们目前只有一个绘图上下文(也可以理解成画布、合成的位图)。如果我们有更多的绘图上下文会怎样,当有脏区域时,相交的绘图上下文相当于是缓存,不需要再绘制子元素,此时绘图的开销自然会降低。
在浏览器中这样的图层叫做Graphics layer或者Composited Layer。
利用Chrome的工具,我们也能看到当前网页有多少合成图层。
如何才能使图层成为合成图层呢
合成图层也不是越多越好,合成图层是有内存开销的,将不常变动的整体设置成合成图层是比较合理的方案。
基于GPU,GPU的众核(普通笔记本512核很正常)架构很适合做一些固定的计算,如视频编解码、矩阵转换(裁剪、放大、缩小、倾斜)、位图合并等。
可以打开链接来感受下Falling Leaves
一个图层对应的位图过大,内部变更后全部重新上传开销太大,为减少传输量,降低内存带宽的消耗,图层采用Tile分组的方式进行管理。
通过Chrome浏览器的控制台我们能很清楚的看到这些过程