要了解浏览器渲染页面的过程,首先得知道一个名词——关键路径渲染。关键渲染路径(Critical Rendering Path)是指与当前用户操做有关的内容。例如用户在浏览器中打开一个页面,其中页面所显示的东西就是当前用户操做相关的内容,也就是浏览器从服务器那收到的HTML,CSS,JavaScript等相关资源,而后通过一系列处理后渲染出来web页面。实际抽象出来理解能够将这些步骤看做一个函数,就输入HTML,通过一层层的处理,最后输出像素。javascript
而浏览器渲染的过程主要包括如下几步:css
具体以下图过程以下图所示:
html
须要注意的是,以上几个步骤并不必定是一次性顺序完成,好比 DOM 被修改时,亦或是哪一个过程会重复执行,这样才能计算出哪些像素须要在屏幕上进行从新渲染。而在实际状况中,JavaScript和CSS的某些操做每每会屡次修改DOM或者CSSOM。html5
值得注意的的是,在每一个阶段,都会有对应的输入,处理,以及输出。下面咱们就来详细的了解一下这几个过程及须要注意的事项。java
由于浏览器没法直接使用HTML/SVG/XHTML,所以当浏览器客户端从服务器那接受到HTML文档后,就会遍历文档节点,而后对这些文档节点经过HTML解析器进行解析,最后生成DOM树,所生成的 DOM 树结构和HTML标签一一对应。须要注意的是,在这其中HTML解析器会进行诸如:标记化算法,树构建算法等操做,其中的规范即遵循了W3C的相应规范,也都有浏览器引擎本身的一些特定的操做,详情能够翻阅这篇很是著名的文章:node
How Browsers Work: Behind the scenes of modern web browsersweb
在此阶段,输入的便是一个HTML文件,而后会有浏览器的HTML解析器对其进行解析,输出树形结构的DOM树。值得注意的是,HTML解析器并非等整个文档所有加载完以后才开始解析的,而是网络进程加载了多少数据,HTML解析器就会解析多少数据。至关与在网络进程与渲染进程之间会在这期间创建一个数据共享的管道,网络进程每次收到数据都会将其转发到渲染进程,从而保证渲染进程中的HTML解析器能够源源不断的获取到用于渲染的数据。这个过程能够理解为下方这个过程:
算法
每一个页面的DOM树,咱们也能够直接经过在控制台输入document 来进行访问:
浏览器
对于DOM树,咱们须要注意如下几点:服务器
document.getElementsByTagName("h2")[0].innerText = "Hello World"
复制代码
此外DOM 树在构建的过程当中可能会被 CSS 和 JS 的加载而执行阻塞,也就是咱们常说的阻塞渲染。这是由于HTML文件是经过HTML解析器转化成 DOM 树的,而在HTML解析器中若是遇到了 JavaScript 脚本,HTML 解析器会先执行 JavaScript 脚本,待这个脚本执行完成以后,再继续往下解析。所以咱们常说,将script标签放在body下面,一般就是基于这种考虑的。但为何CSS也有可能会阻塞DOM树的构建呢,能够看下面一个栗子:
<html>
<head> <style type="text/css" src = "demo.css" /> </head> <body> <p>demo</p> <script> const p = document.getElementsByTagName('p')[0] p.innerText = 'hello world' p.style.color = 'red' </script> </body> </html>
复制代码
因为任何script代码都能改变HTML的结构,所以HTML每次遇到script都会中止解析,等待JavaScript脚本被执行完成以后,再进行接下来的解析,而当咱们经过 JavaScript 去进行样式操做的时候,这个 JavaScript 脚本执行完成的前提条件就成了须要现将样式信息肯定下来。所以在这种状况下,HTML解析器可否继续执行下去,以及继续执行的时间,也须要取决与这个CSS文件给不给面子了。这也是咱们常说的,别在 JavaScript 中操做样式的缘由。
为了优化这种状况,现代浏览器也作了一些优化,好比预解析操做。当渲染引擎接收到字节流后,会开启一个预解析线程,用来分析 HTML文件的代码中的JS,CSS文件,解析到相关文件的时候,预解析进行会提早下载这些资源。
对于处理这种事情,避免阻塞的产生,咱们也有如下几点能够注意的:
在构建渲染树时,须要计算每个呈现对象的可视化的属性值。而这个过程就被称为样式计算或者计算样式。这个过程主要是为了 DOM 树中每一个节点的具体样式,大体可分为三大步骤:
和html一个道理,浏览器也没法直接去理解咱们所写的那些CSS样式,所以浏览器在接收到CSS文件后,会将CSS文件转换为浏览器所能理解的 StyleSheet。转化了的 StyleSheet 咱们一样也能够经过控制台来访问:
在这个过程当中须要注意的是:
在将CSS文转化为浏览器可以理解的 styleSheet 后,就须要对期进行进行属性值的标准化操做了。这里的标准化的意思就是,咱们在写css文件的时候,会写一些语义化的属性好比:red/bold等等。但其实这些词对于渲染引擎来讲,却不是那么好理解的。所以在进行计算样式以前,浏览器还会这对这些不怎么好计算的值进行标准化,将其转化为渲染引擎容易理解的词,好比将red转化成为 rgb(255, 0, 0)等等。
计算出 DOM 树中每一个节点的具体样式主要涉及的就是CSS继承规则和层叠规则了,对于继承规则其实比较好理解,就是,每一个DOM节点都包含的父节点的样式。
而层叠规则也就是样式层叠就有点麻烦了,MDN是这么描述层叠的:
层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称层叠样式表正是强调了这一点。
层叠的具体细节在这里也不展开讲了(我本身如今还没搞清楚。。。),你们能够去CSS层叠看看其内部的一些规则。
在有了css继承规则和层叠规则后,样式计算的这个阶段就会在这两个规则的基础上对 DOM 节点中的每一个元素计算处具体的样式,这个阶段中最终输出的结果会保存在 ComputedStyle 中,这个一样能够经过控制台进行查看:
经过前面两个阶段,咱们已经获得了DOM树以及DOM树中具体每一个元素的样式了,但对于每一个元素所处的几何位置咱们如今仍是不知道的,所以接下来要作的就是计算出DOM树中可见元素的几何位置。这个过程能够分为两个阶段:
因为DOM树还包含不少不可见的元素,好比head标签,script标签,以及设置为display:none的属性,由于浏览器势必不能将全部的dom树的元素都所有拿来进行布局计算,所以在这个阶段,浏览器会额外构建一颗只包含可见元素的布局树。在构建布局树期间,浏览器大致会进行如下一些工做:
下面两个须要注意:
在已经获取了全部可见元素的树以后,就能够计算布局树节点的几何位置了。HTML是基于流的布局方式,所以大多数状况下,只须要进行一次遍历即刻计算出页面的几何信息。一般来讲,处于流靠后的元素不会影响到靠前位置元素的几何特征,所以在进行布局计算的时候,一般是按从左至右,从上至下的顺序遍历文档(只是一般而言,好比表格啥的就不是这样)。
布局计算是一个递归的过程,它从根节点出发,而后递归遍历部分或全部的节点,为每个须要计算的呈现器计算几何信息。这个计算量无疑是庞大的,所以为了不一些较小的更改也会触发页面的总体布局计算,浏览器将布局方式分为了全局布局和增量布局。
在执行完布局计算后,会将布局计算的结果写入布局树中,所以这个过程能够理解为一种装饰者模式,输入输出都是一个布局树,只是在这个过程当中会将布局计算的结果给加进去。
在有了布局树以后,浏览器的仍是不能直接根据布局树来将页面给画出来,由于页面中还存在中一些特殊的效果,好比页面滚动,z-index等。为了可以方便的实现这些花里胡哨的功能,渲染引擎还须要进行一个分层处理,将特定节点生成转筒的图层,并生成一个图层树(LayerTree),这个咱们也能经过浏览器的面板看到:
如上图所示,浏览器的页面实际上被分红了多个图层,这些图层叠加在一块儿就造成了咱们最终所看到的页面。须要注意的是,并非布局树中的每个节点都会包含一个图层,所以若是一个节点没有所对应的图层,那么它就会从属于父节点的图层。若是一个节点须要有本身的图层,一般须要知足如下联合条件
在肯定好图层以后,浏览器的渲染引擎会对图层树中的每一个图层进行绘制,渲染引擎会将一个图层的绘制拆封成不少个小的绘制指令,而后会将这些绘制指令按照必定顺序组成一个待绘制列表。和布局相同,绘制也分为全局和增量两种,也是为了不部分图层的改变而须要对整个图层树进行绘制。此外,CSS也对绘制顺序作了规定:
这里的栅格化是指将图转化为位图。绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际绘制操做是由渲染引擎中的合成线程来完成的。实际过程是当图层对应的绘制列表准备好以后,主线程会将绘制列表提交给合成线程。 合成线程会根据用户所能见的窗口范围对一些划分,将一些大的图层化分为图块。而后合成线程会根据用户所见范围附近的图块来优先生成位图,实际生成位图的操做是由栅格化来执行的。图块是栅格化执行的最小单元,渲染进程维护了一个栅格化的线程池,全部的图块栅格化操做都会在这个线程池里进行。
一般,栅格化会使用GPU进程中的GPU来进行加速,使用GPU进程生成位图的过程叫快速栅格化,经过这个方式生成的位图会被保存在GPU内存中。这样作的好处就在于,当渲染进程的主线程发生阻塞的时候,合成线程以及GPU进程不会受其影响,能够正常运行。这也是为啥有时候主线程卡住了,但CSS动画依然能够风骚依旧的缘由。
在全部的图块都被进行栅格化后,合成线程就会生成绘制图块的命令——“DrawQuad”,而后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,而后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
咱们都知道HTML默认是流式布局的,但CSS和JS会打破这种布局,改变DOM的外观样式以及大小和位置。所以咱们就须要知道两个概念:
须要注意的是,display:none 会触发 reflow,而visibility: hidden属性则并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框,这在咱们上面有提到过。因此visibility:hidden 只会触发 repaint,由于没有发生位置变化。
咱们不能避免reflow,但仍是能经过一些操做来减小回流:
另外有些状况下,好比修改了元素的样式,浏览器并不会马上reflow 或 repaint 一次,而是会把这样的操做积攒一批,而后作一次 reflow,这又叫异步 reflow 或增量异步 reflow。可是在有些状况下,好比resize 窗口,改变了页面默认的字体等。对于这些操做,浏览器会立刻进行 reflow。
结合上文和我看到的一些文章,有如下几点能够优化渲染效率