浏览器的目的是将我们规划好的一段文字、一张图片或者一条直线、一个盒子等组合起来像word一样显示在显示器上。所以可以从显示器工作原理倒推浏览器渲染原理。
1.对于显示器而言,是由一个个物理像素点组成的。而每一个像素点呢,包含红(red)、绿(green)、蓝(blue)三个灯,通过控制每个像素rgb这个三个灯的灯光强弱,我们就可以让计算机展示各种彩色图像了。我们将灯光强弱从0~255分成256个等级,0灯光最弱255灯光最强。因此我们只需要告诉显示器所有物理像素点上rgb灯光的强弱,显示器就能显示我们想要的画面。我们将所有像素点灯光强弱的信息组成的数据称为一“帧”,这一帧的数据存储在“显存”中,显示器每隔几毫秒从“显存”中取出数据进行展示,取数据的这个频率成为“屏幕刷新率”。因此我们如果想让显示器显示我们想要的结果,只需要往显存中写入每个像素点的rgb信息就可以了。如果有看过汇编教程,其中一段操作外设的章节中,应该有往“显存”映射到内存数据段中的地址写入数据,其段地址为oxb800。 如果我们不怕麻烦,可以逐像素的写入显存数据来控制显示器的显示。
2。逐像素的写入显存数据来控制显示器的显示太麻烦了,一般显示器1920*1680这多少个像素点啊,每个像素点的数据都需要手动给出,太多了,而且自上而下给出像素点信息,对于代码编写者如果没有注释完全不知道这些像素点到底会组成什么。因此我们还是需要抽象一下,借鉴一下现实中的绘画,给一支画笔我们可以画一个方框、我们还可以将方框添加填充色,还可以写一段文字,还可以画各种线,有了这样的功能支持我们就可以像在画板上作画那样来绘制“一帧”画面了。而这些绘制指令,如果有用过canvas,那就比较熟悉了。像drawRect、drawText、drawImage等等,我们可以通过chrome-devtools的layers面板,找到任一图层来看到它是如何被绘制出来的。通过截图我们可以到我们按照顺序将一连串的绘制指令依次执行,就能画出我们想要的画面了。注意layers面板中那些柱状图的横向时间轴上可以拖动的,类似performance面板的时间轴,可以通过调整滑动窗口跟拖动时间轴来逐步“回放”绘制过程。
3.有了这些绘制指令,我们来控制显示器显示一幅画面要比逐个像素的去控制要轻松多了,可是依然不够友好,绘制指令是线性的。从上而下依次执行,假如画面中有一个大的绿色盒子中再套一个小的红色盒子,并且小盒子跟大盒子的间隙是5px。这样一个小需求,我们要必须先画一个大盒子然后再根据间距5px来计算小盒子坐标并画出小盒子,如果是先画小盒子再画大盒子那么小盒子就会被覆盖掉了,因此绘制顺序不能变。而且这些绘制指令也体现不出大盒子/小盒子的逻辑关系。 因此我们还是需要继续抽象一下,html跟css就出来了,我们用
<div>
大盒子
<div>
小盒子
</div>
</div>
来表示盒子的逻辑关系用css的padding: 5px来表示大盒子的内边距,这样可读性跟可维护性就好多了。
倒推的大体流程是这样,我们再回过头来看看,其中最重要的一步便是如何从我们web开发者编写的html转换成一条条按顺序排列的绘制指令呢?
1.绘制指令的顺序我们可以从html的逻辑结构来确定,父元素要比子元素先绘制。如果是兄弟元素呢,我们可以先绘制前面的。如果是不同图层呢,z-index低的要比高的先绘制。
2.绘制指令的内容,比如截图中的drawReact(0,0,480,480,pait),其中有一部分是坐标信息,一部分是颜色信息。
2.1 计算坐标信息的这个过程就是我们常说的layout了,由于盒子的坐标信息是互相影响的,比如前面一个盒子变宽/变高了之后,后面的所有盒子位置都可能会发生相应的变化,这时候我们就需要“重排”重新计算所有盒子的位置信息,因此这个开销相对也比较大。
2.1 而如果只是改变了盒子的颜色之类的属性,则不需要重新计算盒子坐标信息,只需要重新生成一下自己元素对应的绘制指令中的颜色部分就可以了。
3.绘制指令的生成我们需要每个盒子的逻辑关系,以及每个盒子坐标信息跟笔触的颜色信息,我们将这些信息以树形结构存储起来。 用树存储是因为树形结构能很好的体现逻辑关系。为什么要存储是因为现在的网页不是最开始的那种一次性渲染完就完事了。现在的网页都是可交互的,我们可以通过js的dom接口来动态修改html。这颗树就是我们说的layout树。
4.layout树中包含了html元素的逻辑关系,跟每个元素的位置信息以及颜色信息。而这些信息是从html跟css中解析出来的,html的解析生成dom树,css解析成css树,然后两者合并成render树。render树中的样式信息是从css中解析出来的,而css中我们只提供了各自元素的宽、高等信息,并没有进行整体的排列布局形成坐标信息,因此必须经过layout这一步进行全局排列布局后方能计算出坐标信息,形成layout树。
5.图层的概念。对于经常变动的元素,比如当我们页面中有一个盒子设置了scroll,这个元素是有滚动条的,当盒子内滚动条滚动时候其实只有盒子内部自己的内容在变化,外部的元素无论是坐标信息还是颜色信息根本都没有变过,为了降低其滚动时的影响,不至于整个页面都重新走一遍渲染流程,浏览器将滚动元素单独设置一个图层,滚动时只需要本图层重新生成绘制指令,然后跟其他图层再合成就可以了。由于每个图层中含有本图层的“位图”信息,而“位图”是每个像素点的rgb信息,一个像素点rgb信息再加上透明度a,就是四个字节,所以占用内存比较大,所以尽量避免滥用图层以免造成“层爆炸”。还是chrome-devtools的layers面板,可以查看每个图层的内存占用情况。
6.GPU加速,通过绘制指令生成的逐个元素的rgb信息,我们将这个信息成为“位图”信息。对于使用GPU加速的图层,GPU会将位图信息存储到GPU中,如果进行一些图像的2D/3D变换等,这些变换其本质是位图信息的所有像素点进行坐标的变换,因为图形的每个点都要重新计算坐标,而GPU的多核机制正擅长做这些任务,所以这些坐标的计算就由GPU来做了,而释放出本就需要执行各种任务的main主线程。