在正式开始讲之前,问我们首先需要讲一下chrome中一些主要的进程
- 浏览器进程,主要负责用户交互,子进程管理和文件存储
- 网络进程是面向渲染进程和浏览器进程等提供网络下载功能
- 渲染进程的主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。
- GPU进程,负责部分光栅化,对video,canvas等标签的渲染上,一些css动画在GPU中绘制等等
- 插件进程,主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
1.用户输入
当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL
- 如果是搜索内容,就会用默认的搜索引擎,生成一个带搜索词的url
- 如果是符合URL规则的内容,那么地址栏就会根据规则,给内容加上协议
2.URL请求
这个时候开始进入资源请求,浏览器进程会把URL发送给网络进程,网络进程这时候会发起URL请求
在发起请求前,网络进程会先去寻找缓存资源,如果有,就直接返回资源给浏览器进程,如果没有,就会开始进行DNS解析->获取到服务器的IP地址->利用IP地址和服务器建立TCP连接->建立之后->浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息->服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程->。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了
1.重定向
网络进程开始开始解析响应头,反向返回的状态是301或者302,那么就说明服务器需要浏览器重定向到其他的URL地址,这个时候网络进程会从Location中重新读取需要重定向的地址,然后发的新的请求
注:301,302都表示重定向,301代表永久性转移,资源不可访问了,浏览器会缓存这个请求,下次访问会直接访问到新地址去,302表示暂时性转移,每次请求原地址都会重定向到新地址去,比较适用于页面的单点登陆
如果返回的是200,那么就表示一切正常,浏览器可以继续处理该请求了
2.响应数据类型处理
Content-Type是一个非常重要的字段,用来告诉浏览器,服务器返回的响应体数据是什么类型的,然后浏览器会根据这个值来决定如何显示
可以看到,访问网站返回的是html格式,这个时候浏览器就会开始准备渲染进程了
3.准备渲染进程
一般情况下,chrome会为每一个页面分配一个渲染进程,但是如果当前页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新开的页面就会复用当前页面的渲染进程。
如果不属于同一站点的话,就会使用单独的渲染流程
渲染进程准备好之后,就会开始进入提交文档阶段
4.提交文档阶段
-
浏览器接收到网络进程的响应头数据吗,向渲染进程发起提交文档
-
渲染进程接收到提交文档的消息,开始和网络进程建立数据传输通道
-
文档传输完成之后,渲染进程返回确认的消息给浏览器进程
-
浏览器进程接收到确认的消息后,开始去更新安全状态,地址栏的url,前进后退的历史状态,同时更新页面
这个过程也解释了为什么你在浏览器输入了一个新的URL,但是老的页面并不会马上消失,而是要加载一会
现在我们要开始进入渲染流程了
3.渲染流程
1.构建DOM树
浏览器无法直接理解和使用HTML,所以需要将Html转换为浏览器能够理解的DOM树结构
2.样式计算
样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成。
1)把css转换为浏览器理解的机构
css样式的来源方式一共分为3种
- 标签内的css
- 元素style属性上的css
- 引入的外部css文件
当渲染引擎接收到css时,会将css文本转化为styleSheets
2)标准化样式表中属性值
现在我们已经拿到styleSheets结构,接下来就要对所有的属性进行标准化
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
上述过程就是属性值标准化->将所有值转换为渲染引擎容易理解的、标准化的计算值
3)计算出DOM树中每个节点的具体样式
现在我们要开始计算DOM树中每个节点的样式属性了
在开始之前,我们首先要讲一下css的继承,css的某些属性具备继承性,在元素上设置这些属性后,它的后代元素就会继承这个属性,当然如果后代元素自己也设置了该元素,那么会优先使用后代元素自己的
接下来还要介绍css层叠样式表,主要作用就是处理多个不同的css样式对同一个html标签进行修改,如何处理优先级的问题。
样式计算最终就通过继承以及层叠计算出了每一个标签最终的样式,并保存在ComputedStyle结构内。
3.布局阶段
1)创建布局树
以上就是构建布局树的流程,通过之前生成的DOM树和ComputedStyle结构,构建一个只包含可见元素的布局树
2)布局计算
这一步的主要工作是知道每一个标签会挂载到哪,计算出标签相对于屏幕的大小和位置,这一步流程过于复杂,我们这次先略过
4.分层
渲染引擎其实是给页面分了很多图层的,这些图层按照顺序叠加到一起,就形成了我们最终看到的页面
这一张图就是布局树与图层树之间的关系,有的节点会有一个对应的图层,但是有的节点没有,没有的节点它的图层就从属于父节点的图层,如图中的span标签
以下两种方式会让元素提升为单独的一个图层
1)拥有层叠上下文属性的元素
- 文档根元素
- position是relative或者absolute的情况下,z-index不为auto
- position是fixed或者sticky
- flex或者grid的子元素,且z-index不为auto
- opacity小于1
- mix-blend-mode属性值不为normal(元素的内容应该与元素的直系父元素的内容和元素的背景如何混合)
- transform,filter(模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像、背景和边框的渲染),backdrop-filter(为一个元素后面区域添加图形效果(如模糊或颜色偏移)),perspective(指定了观察者与 z=0 平面的距离,使具有三维位置变换的元素产生透视效果),clip-path(用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。),mask(通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域),mask-image,mask-border不为none
- isolation是isolate(元素是否必须创建一个新的层叠上下文)
- will-change值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素
- contain属性为layout,paint,或者包含它们其中之一的合成值(元素及其内容尽可能独立于文档树的其余部分)
2)需要被裁剪的地方
``
<style>
div {
width: 200;
height: 200;
overflow:auto;
background: gray;
}
</style>
<body>
<div >
<p>所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下 图:</p>
<p>从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
<p>图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p>
</div>
</body>
现在我们给div设置的是宽高200px,这样的大小并不能完全展示出子元素的内容,这个时候就产生了裁剪,渲染引擎将裁剪出的文字显示在div中
出现这种情况的话,渲染引擎会为文字单独创建一个层,如果还设置了overflow的话,还会为滚动条提升为一个单独的层
5.图层绘制
这一步就是将图层的绘制拆成多个小的绘制指令,然后将这些指令按照顺序组成一个绘制列表。
6.栅格化操作
浏览器的实际的绘制是有渲染引擎中的合成线程来完成的
一个页面可能很大,用户能够看到的那部分我们成为视口。
一个比较大的页面,我们会滚动到很挤才能滚动到底部,那么这个时候就绘制出所有图层的内容的话,就会产生太大的开销,所以合成线程就会将图层划分为图块,这些图块的大小有限制,比如长/宽必须是2的幂次方,最大不能超过2048或者4096等
形成图块之后,合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的
栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上
4.总结
现在我们整理一下一个完整的渲染流程
-
渲染进程将HTML内容转换DOM树
-
渲染引擎将css转换为styleSheets,再计算出每个节点的样式,生成ComputedStyle
-
创建布局树,计算节点的布局信息
-
对布局树进行分层,生成分层树
-
给每个图层生成绘制列表,并提交到合成线程
-
合成线程将图层拆分成图块,并通过光栅化将图块转换成位图
-
合成线程发送绘制图块指令DrawQuad给浏览器进程
-
浏览器进程根据DrawQuad生成页面,并显示到显示器上
5.重排,重绘,合成
1.重排
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),使得部分渲染树(或者整个渲染树)需要重新分析并且且节点尺寸需要重新计算,表现为重新生成布局,重新排列元素,并将其安放在界面中的正确位置,这个过程叫做重排
以下操作会触发重排
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距(margin)、填充(padding)、边框(border)、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如::hover)
- 通过display:none隐藏一个DOM节点
- 给页面中的DOM节点添加动画
可以看到,如果触发了重排,那么就会需要更新一整个完整渲染流水线,这样的开销是最大的
2.重绘
由于节点的样式发生改变,例如改变元素背景色时,屏幕上的部分内容需要更新,表现为某些元素的外观被改变,但没有改变布局。除此之外,重排也必然会触发重绘。
以下操作会发生重绘
- 改变颜色
- 通过visibility:hidden隐藏元素
- 改变border-style
- 添加圆角、阴影
- outline
重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些
3.合成
更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成
比如使用了 CSS 的 transform 来实现动画效果,或者使用will-changetranslate3d、translateZ整个图层的几何变换,透明度变换,阴影。
这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率**