页面的加载和渲染全过程

1,546 阅读7分钟

页面的加载和渲染全过程

  1. HTML 的加载 输入 URL 后,最先拿到的是 HTML 文件。HTML是一个网页的基础,所以要在最开始的时候下载它。 HTML下载完成以后就会开始对它进行解析。
  2. 其他静态资源下载 HTML 在解析的过程中,如果发现 HTML 文本里面夹杂的一些外部的资源链接,比如 CSS、JS 和图片等时, 会立即启用别的线程下载这些静态资源。这里有个特殊的是 JS 文件,当遇到 JS 文件的时候,HTML 的解析会停下来, 等 JS 文件下载结束并且执行完,HTML 的解析工作再接着来。这样做是因为 JS 里可能会出现修改已经完成的解析结果, 有白白浪费资源的风险,所以 HTML 解析器干脆等 JS 折腾完了再干。
  3. DOM 树构建 在 HTML 解析的同时,解析器会把解析完的HTML转化成DOM 对象,再进一步构建 DOM 树。
  4. CSSOM 树构建 当 CSS 下载完,CSS 解析器就开始对 CSS 进行解析,把 CSS 解析成 CSS 对象,然后把这些 CSS 对象组装起来,构建出一棵 CSSOM 树。
  5. 渲染树构建 DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两棵树构建出一棵渲染树。
  6. 布局计算 渲染树构建完成以后,所有元素的位置关系和需要应用的样式就确定了。这时候浏览器会计算出所有元素的大小和绝对位置。
  7. 渲染 布局计算完成以后,浏览器就可以在页面上渲染元素了。比如从 (x1, y1) 到(x2, y2)的正方形区域渲染成蓝色。经过渲染引擎的处理后, 整个页面就显示在了屏幕上。

渲染树(render-tree)的构建

在 DOM 树和 CSSOM 树都渲染完成以后,就会进入渲染树的构建工作。渲染树就是对 DOM 树和 CSSOM 树的结合,得到一个可以知道每个节点会应用什么样式的数据结构。这个结合的过程大体上是遍历整个 DOM 树,然后在 CSSOM 树里查询到匹配的样式。但在不同浏览器里这个过程也不太一样,在 Chrome 里会在每个节点上使用 attach() 方法,把 CSSOM 树的节点挂在 DOM 树上作为渲染树。然而在 Firefox 里,会单独构造一个新的结构,用来连接 DOM 树和 CSSOM 树的映射关系。它们内部的实现方式有所不同,但它们构造出来的渲染树是有很多共同点的。

  1. 渲染树的根是 HTML 节点 在 Google Web Fundamentals 这个文档中,渲染树的根节点是 body,但实际上 HTML 节点上的样式也是可以显示在页面上的,所以我觉得渲染树也应该是由 HTML 节点开始,但是 head 标签里的内容和显示没有关系,所以渲染树中可以没有 head 标签的部分。
  2. 渲染树和 DOM 树 的结构并不完全一致 渲染树里会把所有不可见的元素忽略掉,所以如果是 DOM 树中的节点有 “display: none;” 属性的节点以及它的子节点,最终都不会出现在渲染树中。但是具有 “visibility: hidden;” 样式的元素会出现在渲染树中,因为具有这个样式的元素是需要占位的,只不过不需要显示出来。
  3. 样式优先级关系 同一个 DOM 节点可能会匹配到多个 CSSOM 节点,而最终的表现由哪个 CSS 规则来确定,就是样式优先级的问题了。当一个 DOM 元素受到多条样式控制的时候,样式的优先级顺序应该是、 内联样式 > ID选择器 > 类选择器 > 标签选择器 > 通用选择器 > 继承样式 > 浏览器默认样式

布局(layout)

经过上面的步骤,生成了一棵渲染树,这棵树就是我们展示页面的关键。通过计算渲染树上每个节点的样式,就能得出来每个元素所占空间的大小和位置。当有了所有元素的大小和位置后,就可以在浏览器的页面区域里去绘制元素的边框了。这个过程就是布局,英文中会用 Layout 这个词来描述。

绘制(Paint)

经过布局,每个元素的位置和大小就有了,经过最后绘制这一步,就可以把样式可视化的展现在屏幕上了。在绘制的过程中,浏览器会调用图形处理器,逐层逐块的把所有计算好位置和样式的元素都绘制出来。

重排(Reflow)与重绘(Repaint)

  1. 重排 当我们在 DOM 树中新增、删除了元素,或者是改变了某些元素的大小、位置、布局方式等,在这个时候渲染树里这个有改动的节点和它会影响的节点,都要重新计算。在改动发生时,要重新经历 DOM 的改动、 CSSOM 树的构建、渲染树的构建、布局和绘制整个流程,这个过程就叫做“重排”,也有的叫做“回流”。

以刚才代码中隐藏的 .header 元素为例,假如我们通过 JS 把它的 “display:none;” 属性去掉,那么它就要显示在屏幕中。这种情况下会经历下面的过程

  • DOM 树没有变化。
  • CSSOM 树中这个样式节点里的 display 属性没有了。
  • 布局的过程也会有不小的花销,需要给新加进来的 .header 元素找到位置,然后再把后面影响到的所有元素的大小和位置都重新计算一遍。这样得到一个新的布局值。
  • 最后就是按着新的布局,把 .header 和受它影响的元素都重新绘制一遍,这个页面的改动就生效了。
  1. 重绘 重绘是当我们改变元素的字体颜色、背景色等外观元素的时候,并不会改变它的大小和位置,也不会影响到其他元素的布局,这个时候就没有必要再重新构建渲染树了。浏览器会直接对元素的样式重新绘制,这个过程就叫做“重绘”。

我们还以上面的代码为例,假如我们想对 .content 元素加一个 “color: black;” 的样式。这个时候就会经历以下的过程:

  • DOM 树没有变化。
  • CSSOM 树中 .content 对应的节点加入一条 “color: black;” 的样式。
  • Color 属性的改变不会造成渲染树结构的变化,所以会在现有的渲染树中找出 .content 元素,给它加上 “color: black;” 的样式。
  • 因为存在样式继承机制,所以浏览器还会找到 .content 元素的子元素,如果有可以继承的节点,那么也要给这些节点加上 “color: black;” 的样式,这个例子中就会在 h1.title、p.graph 元素上都加入 “color: black;” 的样式。
  • 不涉及位置变动,布局过程直接忽略。
  • 对 .content 元素及其子元素占用的块重新绘制。
  1. tip

为了减少重排,可以通过几种方式优化:

1、不要逐项的更改样式,可以把需要改动的样式收集到一块,用一次操作改变。

2、可以使用 class 的变动代替样式的改变,也能达到第1条的效果。

3、不要循环操作 DOM,循环的结果也要缓存起来,最后用一次操作来完成。

4、需要频繁改动的元素(比如动画)尽量使用绝对定位,脱离文档流的元素会减少对后面元素的影响。

5、在条件允许的情况下尽量使用 CSS3 动画,它可以调用 GPU 执行渲染。