Chrome浏览器是如何工作的?(二)
七.页面绘制 Paint
经过前面几个步骤,此时完整的页面结构已经差不多可以绘制了。但是还有一个问题在于 Render 进程还不知道你是否在某些 DOM 元素 设置了绝对定位如 display:absolute、sticky 或者调整了一些元素的 z-index 属性。因为我们之前说过,绘制 DOM 的时候,Render 是不关心样式的,所以下一步就是解决这个问题的。
这时 Render 进程的主线程(Main Thread)会去遍历 Layout Tree 从而绘制出一个绘制记录表(Paint Record),来保证页面绘制的顺序不会出错。
当主线程遍历完成后,会生成图层树Layer Tree。
随后 Render 进程的主线程会把设计图交付给合成器线程,合成器线程拿着设计图(Layer Tree)和绘制记录表(Paint Record)将这些信息绘制成人类可以识别的像素点坐标信息。这个过程被称为栅格化。 Rastering
合成器线程的栅格原理并不是从页面的最顶部一直渲染到最底部的过程。而是将整个页面分为多个图块交给多个栅格化线程(Raster Thread),类似于下图这样。
栅格线程将每个图块的栅格化信息保存在 GPU 的内存中。
当整个页面都栅格化完成后,主线程此时将收集栅格线程生成的 Draw Quads 信息,这些信息记录了每个图块在内存中的位置和每个图块需要在页面的哪个位置渲染的信息。(再通俗易懂一点讲的话,可以这样简单理解 const Draw = x 坐标,y 坐标等绘制信息
)
根据这些 Draw Quads 的信息,合成器线程会生成一个合成器帧(Compositor Frame)。然后这个合成器帧会通过 IPC (inter process communication)传送给浏览器进程。就是这个)
紧接着浏览器进程将合成器帧传递给 GPU 进程。(不要忘记我们之前第一篇所说的,浏览器进程负责调度各个进程之间的合作)
最终 GPU 将页面渲染到页面上,恭喜你🎉!经历九九八十一难你终于看到了最终的页面效果。
我们可以在 Chrome 的调试工具下,选择 Performance 选项卡来发现浏览器绘制的每一帧对应的页面。
八.重排(reflow)和重绘(repaint)
此时如果我们开始滚动页面,合成器线程就会再次生成一个合成器帧通过 IPC 传递给浏览器进程,浏览器进程再传递给 GPU , GPU 再次绘制新的一帧,然后呈现出新的页面到屏幕上。此时就会重新进行 Layout 以后的所有步骤。这个过程我们叫做重排(reflow)。由此可见重排的代价是很大的
常见会引发重排的动作比如更改 DOM元素 高度,定位、伪元素等等。(不是本文重点,请自行查阅相关知识)
当页面只是简单的更改了背景颜色等动作时,只会触发样式计算和绘制,并不会进行其它的任务。这个过程我们叫做重绘(repaint)
从上面的图我们可以清晰的看到,无论是重排还是重绘都是进行在主线程上的。别忘了还有一重量级选手 JavaScript 也是运行在主线程上的。那么如果在同一时间进行了大量的重绘重排,其中还穿插着 JS 代码的运行,那么就会造成页面的卡顿。“卡”的产生原因我们继续往下看。
九.渲染帧数
目前我们主流的显示器帧数都是在60HZ以上。也就是说我们电脑的性能支持显示器在 1000ms 内完成60次页面的刷新,(并不是指F5刷新的那个刷新...😂)那么我们1000/60=16.7777
可以得出。浏览器必须在16ms的间隔绘制出新的一帧,才能保证用户觉得“不卡”。
进一步解释就是浏览器要在16ms内完成一次下图的调度过程,用户才能得到最好的体验。那么当主线程 JS 的代码过于耗时的时候,无法及时归还主线程在以便在16ms内进行 Layout和 Layer,那么就造成“卡了一下”的现象。(想象打游戏丢帧)
十.Transform的优点
我们经常会看到建议使用Transform来代替top,left等位置变化属性。其根本原因就是:
Transform 属性完成的动画不会重新 Layout 和 Layer,而是直接运行在合成器线程和栅格线程中的的那也就意味着它不会抢夺主线程的使用,也不会影响JS代码的执行。
饮水思源:
感谢B站up主 objtube的卢克儿 的这期视频,让我通俗易懂的理解了大致的流程。(🍉文章内部分截图来自于这期视频)