hello,大家好。上次为大家介绍了重排和重绘的一些内容,其中涉及到的浏览器渲染流程自己就没有在上篇博文详细介绍了。今天我们就来好好唠唠浏览器的整个渲染流程。
首先,我们知道,一个页面通常由三个部分组成:js,css,html。浏览器拿到这三个文件之后,通过一系列步骤将其渲染到页面上呈现给用户,下面我们就来详细的解释整个流程。
DOM树构建
从你输入网址到拿到服务器返回的数据后,进行的第一个步骤就是将 HTML 解析成DOM 树。为什么要有这一步?因为浏览器是无法识别 HTML 文件的内容的,需要把他转化成浏览器能够理解的 DOM 树。相当于你和一个英国人交流,但是他不懂中文,需要把中文翻译成英文这个英国人才能理解。在这里 HTML 文件就是中文,DOM 树就是英文。
在 chrome 的控制台中输入 document ,我们就能看到什么是 DOM 树
它是存储在内存中的树状结构,能够被 JS 直接操控。
CSSOM 树的构建
CSS 的来源主要有三个
- 外部样式表
- 内部样式表
- 内联样式
同样的,浏览器在拿到 css 文件之后,也需要将转变成浏览器能够理解的形式:styleSheets,它包含了从三个地方取过来的全部样式。
在 chrom 中,我们可以通过 document.styleSheets 来查看该结构
随后浏览器会根据 CSS 的继承规则和层叠规则,将其运用到 DOM 节点中,得到一颗 CSSOM 树,包含了各种节点的样式。
生成渲染树
有了 DOM Tree 和 CSSOM 之后,我们还需要将两者结合生成一颗渲染树。渲染树和这两者之间有什么区别呢?我们用一张图来看看这个过程
可以看到,DOM TREE 和 CSSOM 中存在很多不可见的元素,比如 head 标签和display 值为 none 的 p 标签,在生成 RENDER TREE之后,里面只会保留可见的元素而剔除掉不可见元素。
生成图层树
在生成 RENDER TREE之后,浏览器并不能直接进行绘制,这是为什么呢?熟悉前端的同学肯定知道,在前端开发中有一个层叠上下文的概念。所以浏览器在渲染时同样需要分层,最终将许多图层叠加在一起,就形成了最终呈现给我们的页面。那么,浏览器会如何进行分层?
首先,拥有层叠上下文的元素会被提升为单独的一层,其被称之为渲染层,其主要目的是为了实现层叠上下文。浏览器后期就会根据渲染层由内到外依次叠加,最终呈现给用户。
之所以把渲染层单独指出来,因为其实还存在一种合成层。这个就和我们在重排重绘那篇文章中提到过的如何直接跳到合成阶段的内容有关,也是我们平时说的 CSS3硬件加速。更具体的内容可以查看淘系前端团队分享的文章 无线性能优化:Composite。
绘制列表生成
现在,浏览器终于可以开始干正事了。它会将各个图层的绘制分成很多个小的绘制指令并存入到一个列表中。在 chrome 中我们可以在 Layers 点击某个合成层,随后再点击 Paint Profiler 来查看绘制列表。
就像下面这样
光栅化
Rasterization,光栅化,又称为栅格化,它用于执行绘图指令生成像素的颜色值。
实际上浏览器不是直接对整个图层进行光栅化,它会将图层分块,然后以块为单位进行光栅化。
合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
至此,浏览器的整个渲染的大流程就已经讲解完毕。我们来总结一下这几个步骤:
- 生成 DOM TREE
- 生成 CSSOM
- 生成 RENDER TREE
- 生成图层树,其中包括渲染层和合成层
- 绘制列表生成
- 分块光栅化
- 合成和显示
在了解了整个流程之后,我们再来通过几个常见的小问题加深一下对于浏览器渲染过程的理解。
为什么 JS 文件建议放在 body 的最后而 CSS 文件建议放在前面?
这个问题我相信很多人都知道如何去回答:因为 JS 会阻塞 DOM 树的解析,所以要放在最后,而 CSS 文件要尽早加载,所以要放在最前面。
不错,就是这样的,但是我们结合刚刚所说的浏览器渲染流程:
DOM TREE -> CSSOM -> RENDER TREE
既然 DOM 要解析完才能生成最后的 RENDER TREE,那么放在开头和结尾有区别吗?
首先问是不是,再来问为什么。RENDER TREE真的是要等 DOM 解析完才能生成吗?其实并不是,我们来看一个简单的例子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport&#