浏览器渲染原理(上)这篇文章讲述了,当我们在浏览器中输入一个url是如何得到响应数据的,这篇文章主要讲述,当获取到了响应页面的信息后,是如何将其渲染在屏幕上,呈现在用户眼前的。
在正式内容开始之前,先了解一下几个基本概念。
进程:一个进程就是一个程序的运行实例。启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码,运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。
线程:线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。
线程与进程的几个特点
- 进程中的任意一线程执行出错,都会导致整个进程的崩溃。
- 线程之间共享进程中的数据。
- 当一个进程关闭之后,操作系统会回收进程所占用的内存。
- 进程之间的内容相互隔离。
现代浏览器一般都是多进程的,包括浏览器进程,GPU进程,网络进程,渲染进程,插件进程等多个。
浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能
渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下
GPU 进程。其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程
网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程
插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
所以,当我们打开进程的时候,仅仅打开了一个页面,却可能有多个进程,因为打开了一个页面至少有一个网络进程,一个浏览器进程,一个GPU进程,以及一个渲染进程,还有可能加上插件进程。
我们从服务器获取的html,css,JavaScript他们是如何构建成一个完整的页面的呢?
1、构建DOM树
浏览器是不能直接理解和使用html的,需要将 HTML 转换为浏览器能够理解的结构——DOM 树。也就是说,html需要经过html解析器,最终输出树状结构的 DOM。
浏览器首先将html的原始字节数据转换为文件指定编码的字符,这种转换是基于html文件的字符编码完成的,字符会被进一步解析为‘标记(token)’,也有人叫令牌,token记录了标签的开始与结束,通过这个特性可以轻松判断一个标签是否为子标签等。解析为标记(token)后,会将每个token转换成定义其属性和规则的节点对象,这些节点对象就是将被链接到称为DOM的数据结构中,DOM简历起了父子关系,相邻兄弟关系等,在这个 DOM 对象中,每个节点之间都建立了关系,也就是树状结构的 DOM。
整个DOM树的构建过程其实就是:
字节 -> 字符 -> 标记(token) -> 节点对象 -> 对象模型
DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
2、构建CSSOM和样式计算
css样式的主要来源有是那种,link引入外部css文件,style标签的css,元素的style属性内嵌的css。
当HTML代码遇见标签时,浏览器会发送请求获得该标签中标记的CSS文件(使用内联CSS可以省略请求的步骤提高速度,但没有必要为了这点速度而丢失了模块化与可维护性),
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当浏览器接收到 CSS 的原始字节时,会启动一个和处理 HTML 原始字节类似的过程。就是说,原始数据字节被转换成字符,然后标记,然后形成节点,节点形成css的树结构,称为css对象模型,也叫CSSOM。
CSSOM定义了一些API,web开发者使用这些API就可以进行检查,更改文档及文档视觉属性,包括布局定位,视图宽度等。
比如当我们在控制台输入document.styleSheets
这个样式表包含了很多种样式,已经把那三种来源的样式都包含进去了。渲染引擎会把获取到的 CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能。
转换样式表中的属性值,使其标准化
CSS 文本中有很多属性值,如 2em、blue、bold、这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。
2em 被解析成了 32px,red 被解析成了 rgb(255,0,0),bold 被解析成了 700……
样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性。
计算出 DOM 树中每个节点的具体样式
每个节点的具体样式,需要综合三种css来源对标签的描述,利用css继承、css优先级、css层叠规则等计算出来,我们可以在chrome浏览器的Element标签中看到
这里划线的表示没有采用的样式,如果想看到一个标签最终应用的全部样式,可以在Chrome浏览器的Element标签下,选中摸个标签,点击computed,即可看到应用在这个标签下的所有格式化好的样式。
3、构建渲染树
DOM树描述了文档的结构与内容,CSSOM描述了对文档应用的样式规则,浏览器拥有了这两个对象集合之后最终会将两者结合在一起,形成渲染树。
浏览器会先从DOM树的根节点开始遍历每个可见节点,并把这些节点添加到渲染树中。
不可见的节点不会渲染到页面上,比如CSS设置了display: none属性的节点,值得注意的是visibility: hidden属性并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,所以它会被渲染成一个空框
对每个可见节点,找到其适配的CSS样式规则并应用。 - 渲染树构建完成,每个节点都是可见节点并且都含有其内容和对应规则的样式。
3、布局阶段
当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
这时候css盒模型就闪亮登场了,CSS采用了一种叫做盒子模型的思维模型来表示每个节点与其他元素之间的距离,盒子模型包括外边距(Margin),内边距(Padding),边框(Border),内容(Content)。页面中的每个标签其实都是一个个盒子
布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对的测量值也都会被转换为屏幕内的绝对像素值。
现在我们有了布局树,也知道了每个标签的位置信息,就会进行下一步,分层
4、分层阶段
其实浏览器的页面实际上被分成了许多层,这些图层叠加后才形成了最终的页面。
比如一些复杂的css动画,z-index等,渲染引擎会为他们生成专用的图层,并生成对应的图层树。在chrome的Layers下 可以可视化页面的分层情况,以B站为例,如图所示:
通常情况,并不是布局树的每一个节点都包含一个图层,如果节点没有对应的图层,那么这个节点就从属于父节点的图层,也就是说,每个节点都会直接或间接的从属于某一个图层。
如何提升单独的图层?
1、拥有层叠上下文属性
比如,明确定位的元素,定义透明的元素,使用css滤镜的元素等等,具体可参考层叠上下文
2、需要被裁剪的地方也会被创建单独的图层
比如说一个200*200的div元素,如果文字内容过多,超出了元素面积,就会产生裁剪,渲染引擎会把裁剪的一部分内容用于现在在div中,出现这种情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。
5、图层绘制、分块、光栅化和合成
构建完图层之后,渲染引擎会对图层树中的每个元素进行绘制,首先,会把一个图层的绘制拆分成很多小的指令,再把这些指令按照顺序组成一个待绘制列表,并将其提交给合成线程,合成线程将图层分成图块(因为一个页面可能很大,而用户只能看到视口中页面的一部分,如果全部绘制开销会很大,所以合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图),并在光栅化线程池中将图块转换成位图,然后发送绘制图块命令给浏览器进程,浏览器进程根据 绘制图块命令生成页面,并显示到显示器上。
一个完整的渲染流程图大致如下
了解了浏览器渲染的整个流程,有助于我们回顾工作中没有注意的知识点,以及如何优化页面的渲染。
css优先级
上文说到构建CSSOM的时候会计算每个节点的具体样式,从而引出css优先级。
1、important 表示强制应用该样式,用来覆盖内联样式和权重比较高的选择器的样式
2、如果比较后权重相同,那么后者覆盖前者,后渲染的胜出;
3、内联样式 > id选择器样式 > 类选择器样式 > 元素选择器样式;
css硬件加速
所谓硬件加速就是将浏览器的渲染过程交给GPU处理,而不是使用浏览器的渲染引擎。这样就可以使得animation与transition更加顺畅,但是CSS animations, transforms 以及 transitions 不会自动开启GPU加速,我们可以对元素使用一些css规则去触发硬件加速,比如transform: translateZ(0)
.cube {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
/* Other transform properties here */
}
在 Chrome and Safari中,当我们使用CSS transforms 或者 animations时可能会有页面闪烁的效果,下面的代码可以修复此情况:
.cube {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-perspective: 1000;
-moz-perspective: 1000;
-ms-perspective: 1000;
perspective: 1000;
/* Other transform properties here */
}
在webkit内核的浏览器中,另一个行之有效的方法是
.cube {
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
/* Other transform properties here */
}
不过使用GPU可能会导致严重的性能问题,因为它增加了内存的使用,而且它会减少移动端设备的电池寿命。
css盒模型
在布局阶段,为了定位每个元素在页面具体大小与位置,引出了css盒模型。
标准盒模型下盒子的大小 = content + border + padding + margin
怪异盒模型下盒子的大小=width(content + border + padding) + margin
可以用box-sizing来设置盒子模型的解析模式
content-box: 默认值,border和padding不算到width范围内,可以理解为是W3c的标准模型(default)
border-box:border和padding划归到width范围内,可以理解为是IE的怪异盒模型
padding-box:将padding算入width范围
同时根据盒模型还能引出,块级上下文(BFC),上下margin重合等各种布局问题,可以百度css面试题会有很多专门整理的css面试题,例如CSS经典面试题(史上最全,持续更新中...)
重绘和重排
更新元素的几何属性:重排
当Render Tree中部分或全部元素的尺寸,结构,或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为重排
如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
会导致重排的操作:
1、页面首次渲染
2、添加或删除课件的DOM元素
3、元素位置改变
4、元素内容改变(例如:一个文本被另一个不同尺寸的图片替代)
5、浏览器窗口尺寸改变
更新元素的绘制属性:重绘
当页面中的元素样式的改变并不影响它在文档流中的位置时候,(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘
相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
针对css:
1.避免使用table布局
2.尽可能在DOM树的最末端改变class
3.避免设置多层内联样式
4.将动画效果应用到position属性为absolute或fixed的元素上
5.避免使用css表达式(calc())
所以当实现动画的时候,使用css3属性比使用定位更能优化性能
transform 实现动画效果,可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作,相对于重绘和重排,合成能大大提升绘制效率。
本文参考:
CSSOM中文资料