输入一个URL到页面显示-渲染篇

791

前言

优化页面的一个重要的方法是减少重排和重绘。虽然框架在这方面作的很好了,但是我们难免有去操作 DOM 的时候。了解渲染流程能让你看透页面是如何工作的。优化代码的时候才能更得心应手

构建 DOM

浏览器加载网页时会收到对应的 HTML 文件。因为浏览器无法直接理解和使用 HTML 所以需要将其转换为浏览器能理解的结构——DOM

HTML 首先经过 tokensier(标记化)将字符串转换为 Token 同时会标识出当前 Token 是 startTag 还是 endTag,亦或是文本等信息。

1659666542065.jpg

HTML 解析器会在内部维护一个栈,同时会创建一个 document 的空 dom 节点,并将 document startTag 入栈。

当遇到文本(非元素节点) Token 时会向 DOM 结构中插入一个文本节点,但不会放入栈中。

当遇到一个 startTag token,会创建一个相应的 dom 节点,插入 DOM 结构中,并且将 startTag 压入栈中。

1659795858884.jpg

当遇到一个 endTag token 时,HTML 解析器会查看栈顶的 token,是不是其对应的 startTag token,如果是,则弹出,表示该元素已经解析完了。

直到最后 document 的 endTag token 出栈,整个文件就算解析完了。

CSSOM 树

有了 DOM 树之后,还需要每个节点的计算样式,这样浏览器才知道如何摆放这些节点。所以需要构建 CSSOM 树。

CSSOM 的构建与 DOM 树的构建非常类似,不同之处在于计算样式涉及层叠与继承。

布局树

有了 DOM 树与 CSSOM 树接下来进入布局阶段,这一阶段浏览器要做的事情是创建布局树和弄清楚各个节点在页面中的确切位置和大小。

合成布局树浏览器做了下面这些工作:

  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中
  • 忽略不可见节点,例如 head 标签和 display:none 的元素

布局的计算过程非常复杂,有兴趣的同学请自行查阅

分层

页面通常会包含复杂的 3D 变换、脱离文档流的元素、z-index 做 z 轴排序等。如果不为这些节点创建正确的图层顺序就会出现意料之外的错误。

一般来说拥有层叠上下文属性的元素会被提升为单独的一层。如果一个节点没有对应的层,那么这个节点就从属于父节点的图层

图层绘制

浏览器会遍历布局树生成图层树,并对图层树中的每个图层进行绘制,创建绘制指令列表。绘制指令列表其实就是描述如何绘制一个节点。

1659796450853.jpg

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程进行栅格化。

栅格化

栅格化过程中有一个重要的点是,通常一个页面可能很大,但是用户只能看到其中的一部分。在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

基于此,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512

图块会被发送给栅格线程,栅格线程栅格化每个图块,将其存储在 GPU 内存中

合成和显示

一旦所有图块都被光栅化,合成线程就会收集称为 draw quads 的图块信息,这些信息记录了图块在内存中的信息和在页面哪个位置绘制图块的信息,根据这些信息合成器线程形成了一个合成器帧,通过 IPC 传送给浏览器进程,浏览器进程将合成器帧发送到 GPU,接着 GPU 渲染,然后就可以看到页面了 。

阻塞

HTML 解析过程中会遇到各种资源,其中最重要的是 CSS 和 JS。CSS 析并不会阻塞 HTML 的解析,但是会阻塞渲染。因为我们必须要知道节点的具体大小和位置信息才可以渲染。而 JS 会阻塞 HTML 的解析,究其原因是 JS 有改变 DOM 的能力,如果我们一边解析一边修改 DOM 那么 HTML 的解析将会毫无意义。这也是为什么要把 JS 放在正确的位置或使用合适的关键字的原因。

总结

如果修改了元素的几何属性,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。重排需要更新完整的渲染流程,所以开销也是最大的。

如果修改了元素的绘制属性,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。