一、引入
作为一名前端开发人员,和自己打交道最多的就是浏览器了,那么当浏览器拿到我们的写的代码后,是如何工作的呢?这个过程,就是浏览器的渲染机制,今天我们来好好分析一下。
二、浏览器组成部分
一个浏览器都包含那些元素部分呢?
- 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的你请求的页面外,其他显示的各个部分都属于用户界面。
- 渲染引擎 -解析DOM文档和CSS规则并将内容排版到浏览器中显示有样式的界面,也有人称之为排版引擎,我们常说的浏览器内核主要指的就是渲染引擎
- JS解释器-用来解释执行JS脚本的模块,如 V8 引擎、JavaScriptCore
- 网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
- UI 后端 -用来绘制基本的浏览器窗口内控件,如输入框、按钮、单选按钮等,根据浏览器不同绘制的视觉效果也不同,但功能都是一样的。
- 数据存储 这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5)定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。
- 浏览器引擎 - 在用户界面和渲染引擎之间传送指令。
三、浏览器内核
浏览器中最重要的部分就是浏览器内核,也就是渲染引擎,这个引擎是浏览器的核心,它的开发难度极大,内核代码大概有2400万行代码,可见其复杂程度极大。
1.浏览器渲染引擎承担什么任务呢?
渲染引擎,负责对网页语法的解释(如HTML、JavaScript)并渲染网页。渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。
ps:不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。
2.具体渲染的流程是什么?
概括:浏览器的渲染概念就是html解析成DOM,css形成样式规则。两者共同构建渲染树。浏览器根据渲染树的样式进行布局和渲染。
-
当浏览器拿到HTML文档时首先会进行HTML文档解析,构建DOM树。
-
遇到css样式如link标签或者style标签时开始解析css,构建样式树。HTML解析构建和CSS的解析是相互独立的并不会造成冲突,因此我们通常将css样式放在head中,让浏览器尽早解析css。
-
当html的解析遇到script标签会怎样呢?答案是停止DOM树的解析开始下载js。因为js是会阻塞html解析的,是阻塞资源。其原因在于js可能会改变html现有结构。例如有的节点是用js动态构建的,在这种情况下就会停止dom树的构建开始下载解析js。
脚本在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JavaScript 引擎;等 JavaScript 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建。而因此就会推迟页面首绘的时间。可以在首绘不需要js的情况下用async和defer实现异步加载。这样js就不会阻塞html的解析了。当HTML解析完成后,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。注意,异步执行是指下载。执行js时仍然会阻塞。
-
在得到DOM树和样式树后就可以进行渲染树的构建了。应注意的是渲染树和 DOM 元素相对应的,但并非一一对应。比如非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)
3. 渲染详解
-
HTML解析分为标记化和树构建阶段
- 标记化:这个的意思就是实现标签的提取。后台给前端传回的信息是一个一个的字符流。标记化就是通过识别前后尖括号,来区别哪一部分是标签,哪一部分是内容
- 树构建:将提取的标签,添加到树结构中。同时这些元素还会存储在堆栈中,用于查找和纠正嵌套错误。在此阶段完成后,浏览器会将文档标注为交互状态。随即绑定的事件可以被触发了
这里的交互状态是指,当浏览器已经开始执行js代码后,即使js代码还没有执行完,这时已经绑定的事件是可以被触发的。
-
CSS解析
-
css是逆向解析,也就是说我们应该
-
在 Webkit 中没有规则树,因此会对匹配的声明遍历 4 次。首先应用非重要高优先级的属性(由于作为其他属性的依据而应首先应用的属性,例如 display),接着是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。这意味着多次出现的属性会根据正确的层叠顺序进行解析。最后出现的最终生效。
-
-
渲染树
- 样式树和DOM树连接在一起形成一个渲染树,渲染树用来计算可见元素的布局并且作为将像素渲染到屏幕上的过程的输入。
- 布局使用流模型的Layout算法。所谓流模型,即是指Layout的过程只需进行一遍即可完成,后出现在流中的元素不会影响前出现在流中的元素,Layout过程只需从左至右从上至下一遍完成即可。
- 但实际实现中,流模型会有例外。Layout是一个递归的过程,每个节点都负责自己及其子节点的Layout。Layout结果是相对父节点的坐标和尺寸。其过程可以简述为:
- 父节点确定自己的宽度
- 父节点完成子节点放置,确定其相对坐标
- 节点确定自己的宽度和高度
- 父节点根据所有的子节点高度计算自己的高度
- 此时renderTree已经构建完毕,不过浏览器渲染树引擎并不直接使用渲染树进行绘制,为了方便处理定位(裁剪),溢出滚动(页内滚动),CSS转换/不透明/动画/滤镜,蒙版或反射,Z (Z排序)等,浏览器需要生成另外一棵树 - 层树。因此绘制过程如下:
- 获取 DOM 并将其分割为多个层(RenderLayer)
- 将每个层栅格化,并独立的绘制进位图中
- 将这些位图作为纹理上传至 GPU
- 复合多个层来生成最终的屏幕图像(终极layer)。
四、浏览器显示优化
1.相关定义
- Repaint(重绘)——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
- Reflow(重排)——意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。reflow 会从这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置。
- 首屏时间——当浏览器显示第一屏页面所消耗的时间,在国内的网络条件下,通常一个网站,如果“首屏时间”在2秒以内是比较优秀的,5秒以内用户可以接受,10秒以上就不可容忍了。
白屏时间 - 白屏时间——指浏览器开始显示内容的时间。但是在传统的采集方式里,是在HTML的头部标签结尾里记录时间戳,来计算白屏时间。在这个时刻,浏览器开始解析身体标签内的内容。而现代浏览器不会等待CSS树(所有CSS文件下载和解析完成)和DOM树(整个身体标签解析完成)构建完成才开始绘制,而是马上开始显示中间结果。所以经常在低网速的环境中,观察到页面由上至下缓慢显示完,或者先显示文本内容后再重绘成带有格式的页面内容。
2.优化办法
原理:了解浏览器如何进行解析,我们可以在构建DOM结构,组织css选择器时,选择最优的写法,提高浏览器的解析速率。理解浏览器如何进行渲染,明白渲染的过程,我们在设置元素属性,编写js文件时,可以减少”重绘“”重新布局“的消耗。
-
1、减少资源请求的次数和压缩数据内容。 因为资源的请求是一个复杂的过程。网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要快。所以,请减少HTTP请求。
①进行资源打包,将需要多次请求的资源进行打包减少资源请求次数,如webpack等。
②使用雪碧图,可以避免因不同图片引起的多次资源下载 -
2、高效合理的css选择符可以减轻浏览器的解析负担。
因为css是逆向解析的所以应当避免多层嵌套。
避免使用通配规则。如 *{} 计算次数惊人!只对需要用到的元素进行选择
不要去用标签限定ID或者类选择符。如:ul#nav,应该简化为#nav
尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。
考虑继承。了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则 -
3、从js层面谈页面优化
①解决渲染阻塞
如果在解析HTML标记时,浏览器遇到了JavaScript,解析会停止。只有在该脚本执行完毕后,HTML渲染才会继续进行。所以这阻塞了页面的渲染。
解决方法:在标签中使用 async或defer特性
②减少对DOM的操作
对DOM操作的代价是高昂的,这在网页应用中的通常是一个性能瓶颈。
解决办法:修改和访问DOM元素会造成页面的Repaint和Reflow,循环对DOM操作更是罪恶的行为。所以请合理的使用JavaScript变量储存内容,考虑大量DOM元素中循环的性能开销,在循环结束时一次性写入。
减少对DOM元素的查询和修改,查询时可将其赋值给局部变量。
③使用JSON格式来进行数据交换
JSON是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式。同时,JSON是 JavaScript原生格式,这意味着在 JavaScript 中处理 JSON数据不需要任何特殊的 API 或工具包。
④让需要经常改动的节点脱离文档流
因为重绘有时确实不可避免,所以只能尽可能限制重绘的影响范围。