浏览器渲染原理
浏览器渲染原理
渲染进程
浏览器会有五大进程,
-
浏览器主进程
主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
-
GPU进程
Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
-
网络进程
主要负责页面的网络资源加载。
-
多个渲染进程
核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
-
多个插件进程
主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
传输协议
浏览器端数据的传输大多都是HTTP协议,而HTTP又是基于TCP/IP协议的。
IP协议
计算机唯一住址,有了这个地址,我们才能将消息发送到另外台电脑上。
用户数据包协议(UDP)
有了IP协议,我们能将信息发送到另外一台电脑上,但是这段消息具体哪个程序来接受处理呢? 这又需要用到UDP,该协议用端口来区分运行在同一台设备上的多个应用程序。 我们所发送的每一段信息都会被计算机传输层和网络层处理,加上信息头,信息头里会包含自己的IP+端口和对方的IP+端口。有了上面的IP+ 端口,我们能顺利的将一个消息发送到具体的程序中去。UDP很快,但是同时有个很大的问题就是不可靠。 所以在某些场景下他就不太适合。 那么我们应该如何保证数据的完整性呢?
传输控制协议(TCP)
TCP和UDP类似,但他是一种面向连接的、可靠的、基于字节流的传输层通信协议,他在数据传输的时候会经过三次握手,确认链接后才进行数据的传输, 每次接到数据后也必须响应,数据传输完毕后也会通过四次挥手来结束链接。 显然这样的代价就是低效。
从URL到页面呈现, 期间浏览器做了什么?
浏览器是如何处理用户请求的? 这是一个金典的面试题。下面就来聊一聊
1. 处理用户输入
浏览器接收到用户输入,主进程会进行判断当前是URL还是关键字, 如果是关键字则会交给你指定的搜索引擎,搜索引擎则会通过一定的规则进行解析重组成一个URL,如果是URL则会加上协议,拼合成完整的URL。
2. 发起请求
完成第一步,浏览器会将URL交给网络进程,由网络进程发起真正的请求,过程如下:
- 检查本地是否有缓存,有则直接取缓存内容
- 没有缓存则会发起DNS解析,获取到域名对应的IP
- 建立TCP联接,将信息带上各种请求头发送给服务端
3. 获取响应
获取响应信息,解析响应头,根据Content-Type
的值的类型来判断浏览器怎么处理响应的数据,如果是页面,则会交给渲染引擎, 如果是下载,则会交给下载管理器
4. 准备渲染进程
一般情况下,每个Tab都会含有一个渲染进程。如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
5. 提交文档
- 主进程接到头数据,发送“提交导航”给渲染进程
- 渲染进程接到消息,便开接受数据,接受方式是直接和网络进程建立消息通道
- 消息接受完毕后,渲染进程向主进程 “确认提交”。
- 主进程收到消息后,开始移除之前旧的文档,开始更新页面状态和展示信息。
6. 渲染阶段
渲染就是把html
、css
、js
转换成页面的过程。 这个过程比较复杂,分为了很多个阶段,每个阶段都接受一个输入,经过自身的处理后,输出内容,然后进入下一个阶段。
大概会有如下几个阶段:
构建 DOM 树 => 样式计算 => 布局阶段 => 分层 => 绘制 => 分块 => 光栅化和合成
6.1、构建DOM树
构建DOM树就是将我们写的HTML文档转换成浏览器能够识别的DOM树结构,该DOM树保存在计算机内存中,所以我们可以通过 document
相关的API来操作更改DOM。
6.2、样式计算
通常我们写的css会有三种类型:
- 以
.css
为后缀的文件,通常用link
标签引入 - 以
style
标签包裹的样式 - 以
style
熟悉内嵌到html的样式
样式计算目的是计算出每个DOM节点的样式。
- 首先渲染引擎会将我们写的
css
转换成浏览器能够理解的styleSheets
格式。
-
其次便是值的标准化,因为我们在css文件中写的值是多种多样的, 比如大小单位有
px
、em
、rem
,颜色可以有多种表示方式。 这些为了友好编程而生的写法最后都是需要转换成标准的浏览器能识别的值的。 -
得到DOM每个节点最终的样式,因为css样式可以继承, 所以比如你用了某个框架的
Button
按钮, 这个然后为这个按钮加了一个自己喜欢的颜色。这时候这个按钮最终的样式则会有: 你定义的样式 + 框架定义的样式 + 浏览器默认的样式。 渲染引擎回去弄清楚这其中的复杂继承关系,然后得到最终的一个结果。我们可以通过浏览器端的Computed
标签查看到每个节点的最终样式
6.3、布局阶段
布局阶段就是计算出每个DOM节点在屏幕上的几何位置。分为如下两步:
- 生成一个布局树。
布局树 = DOM树 - 不可见元素
不可见元素: 设置了display: none
样式的元素
- 计算布局树节点的几何位置
6.4、分层
因为页面中会有复杂的效果,比如我们设置的3D变换,滚动,z-index
排序等,所以在将布局树绘制前我们还需要给他分层。生成图层树,类似PS的图层。分层规则如下:
- 拥有层叠上下文属性的元素会被提升为单独的一层。 如 position, transform...
- 内容会别裁剪的节点。
overflow
6.5、绘制
绘制就像画画一样,也是分步骤的,渲染引擎会将图层的绘制拆分成很多小的绘制指令, 每个指令对应着一个操作:如画一个矩形。 然后这些一个个的指令又组合成一个列表 —— 待绘制列表。
tips: 页面绘制过程可通过 Layers
标签栏查看。
6.5、分块和光栅化
当绘制列表准备完成后,则会将列表交由渲染引擎中的合成线程,由该线程来完成绘制工作。
通常页面都会屏幕的可见区域大,所以为了能让我们尽快的看到东西, 所以合成线程会把图层进行分块,通常大小为 512x512 或者 256x256。 然后会优先绘制可见区域的区块,绘制实际就是将区块转换成位图。这个转换操作又是靠栅格化来执行的。 通常栅格化都会用GPU来加速,生成的位图会保存在GPU中
6.5、合成显示
当所有的区块都栅格化完毕,合成线程则会将结果发送给浏览器主线程, 主线程有个叫 viz
的组件则会将接收到的页面内容放到内存中, 然后再显示到屏幕上。
至此, 一个完整的页面则显示渲染完毕。