[浏览器]带你掌握页面渲染流程

444 阅读8分钟

最近想重新系统地整理一下前端的知识,因此写了这个专栏。我会尽量写一些业务相关的小技巧和前端知识中的重点内容,核心思想。

前言

上一篇文章我们讲了浏览器的导航流程(点击回顾),今天我们顺着这个逻辑,来看看浏览器在得到页面资源之后是怎么渲染的。

渲染流程

我们先来想想,构成一个页面的核心资源有哪些?HTML,js,css。可是对于浏览器来说这些文件其实都只是字符,要把他们真正渲染成页面之前需要先解析合成图层。这个过程就是页面的渲染流程。

流程大致为:

下面我们就来详细讲一下这些步骤。

构建DOM(Document Object Model)树

浏览器是无法直接理解html文件的,所以第一步是要把html的标签,属性等信息转换为【dom树】。所谓的dom树,是一推存在内容里的树状结构的数据,以html为根节点往下延申。同时,dom树的内容实际上跟html的标签的内容是很像的,他主要是存放标签之间的层级关系和每个节点的属性信息。

控制台可以打印输出dom:

DOM的作用
  • 从页面的视角来看,DOM 是生成页面的基础数据结构。
  • 从 JavaScript 脚本视角来看,DOM 提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容。
  • 从安全视角来看,DOM 是一道安全防护线,一些不安全的内容在 DOM 解析阶段就被拒之门外了。

构建CSSOM(CSS Object Model)树

同理,浏览器是无法直接理解css的,他会先把css解析成【cssom树】。但构建cssom的步骤会稍微多一点:

1.把css转成styleSheets

不管是来自与标签内嵌的样式,用link引用的外部css还是用style标签写的样式。最终都会转到styleSheets中。可以在控制台打印输出styleSheets:

2.标准化样式的值

css允许我们写一些便捷写法,如颜色可以直接写red,blue,字寛可以写bold等。但浏览器在最终渲染时,其实还是需要把这些内容转换成它理解的。

color: blue; ----> color: rgb(0,0,255);
font-weight: bold; ----> font-weight: 700;
3.结合dom算出每个节点的具体样式

我们都知道css本身是没有页面节点的完整数据的,因此在构造最终cssom的时候,需要借助dom树的层级关系。在构建时,如果当前节点的样式在styleSheets中有明确的数据,就会标注在cssom上。如果没有的话,会从上层节点中继承过来。这也就是css继承的来源。

4.构成cssom

在完成上述步骤之后,我们就构建出了一棵带有样式信息和部分节点层级结构的数据树了。

cssom的作用

  • 提供给 JavaScript 操作样式表的能力,
  • 为布局树的合成提供基础的样式信息。

构建布局树

现在我们有了一棵dom树和一棵cssom树了,下一步是需要把他们2者结合,创建【布局树】。我们都知道尽管dom的数据很完整,但并不是所有内容都是可见的。对于不需要显示的内容,浏览器可能不关心的。这就是为什么还会有一棵布局树的原因。

布局树会把head等不需要显示的标签内容和带有dispaly:none样式等被隐藏的节点忽略。只保留需要显示的内容,并把每一项在页面中的位置信息算出。

总的来说布局树的作用就是:

  1. 只保留忽略部分dom节点,只保留需要展示的内容。
  2. 计算出布局树中每一项的具体位置信息。

分层

布局树并不是浏览器渲染的最终数据。其实我们平常看到的页面虽然是一个个的平面,但在浏览器内部实际上由一层层图层构成的。之所以要把页面分层,是为了可以更好地实现一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等动画效果。 因为浏览器可以更大胆地对那部分的内容作动画处理,不用担心影响到别的图层。

分层的条件

并不是所有的节点都会被分层,一般满足以下条件的才会被分层:

  1. 拥有层叠上下文属性的元素,一般明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都会让元素拥有层叠上下文属性。
  2. 需要剪裁(clip)的元素。如我们的给某个元素定了宽高,当内容超出范围时滚动,就会触发【剪裁】。这种情况也会分层。
浏览器的分层信息

我们可以通过浏览器的layers标签,看到页面分层的信息。有兴趣的朋友可以自行了解。

栅格化(raster)

通常一个页面可能会很大,用户在浏览时需要滚动才能看到完整的信息。这样对与浏览器来说,一次渲染完所有的画面开销就很大了。因此浏览器会把完整页面分成很多个小的图块。这些图块的大小通常是 256x256 或者 512x512。

而根据用户能看到的窗口(也叫视口)生成位图,的过程就叫【栅格化】。

合成和显示

当所有图块都栅格化完成之后,渲染进程就会通知浏览器进程。浏览器将其页面内容绘制到内存中,最后再将内存显示在屏幕上。然后完整整个页面渲染的流程。

综合

综合上述内容,我们可以总结一下页面渲染流程:

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树。
  5. 通过光栅化把每个图层中将图块转换成位图。
  6. 渲染进程通知浏览器进程生成页面完成,并显示到显示器上。

二次渲染!

看完上述内容之后我们得知了页面从获取html资源到首次渲染的完整流程。可是在运行的过程中,页面实际上并不是固定的,js可以通过修改dom,css等让页面二次渲染。这里就会涉及到2个很常见的名词 重排】【重绘】。 它们同时也是高频的面试题,我们就根据页面的渲染流程来看看他们的区别。

重排

重排指的是更新了元素的几何属性的情况。如修改了元素的宽高,位置等几何信息。从流程图中我们可以找到,重排的执行位置。

可以看到重排几乎会触发整个渲染流程,所有无疑他的开销成本是最大的。

重绘

重绘是指更新元素的绘制属性的情况 如修改背景色,字体颜色等不会修改几何属性的情况。

由于没有修改几何属性,重绘不会触发布局和分层部分的流程,因此渲染成本会比重排更小。

合成?

【合成】这个词出现的频率好像不高,其实是指渲染引擎将跳过布局和绘制,只执行后续的合成操作的过程。 最常见的情况就是CSS 的 transform,这些情况就像是在某一图层做动作(如动画滚动等),渲染引擎会直接在合成线程上完成工作(这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因)。在不占用主线程之余,渲染效率比重排和重绘都高不少。

总结

今天我们了解了浏览器页面渲染流程的具体环境,由此再来解释了重排,重绘影响渲染效率的原因。并提出了合成的优化方案。希望大家能有所收获。

其实这部分的内容网络上有很多的文章,但是李兵老师的《浏览器工作原理与实践》讲得非常通俗易懂,本文的大部内容都是参考该课程总结的,大家有兴趣的也可以去看看。

参考

《浏览器工作原理与实践》——李兵 (本文部分文字及图片取于该课程)