我们经常写 HTML 、 CSS 和 JavaScript ,写好这些之后,我们就会在浏览器中看到页面,那浏览器究竟在这背后做了一些什么事情呢?本篇文章将揭晓答案!
了解浏览器的渲染原理是我们在通往更深层次的前端开发中不可缺少的,它可以让我们从更深层次、角度去考虑性能优化等~
下面进入正文~
进程、线程
浏览器会分配一个线程“自上而下,从左到右”依次解析和渲染代码,那么进程和线程是什么,它们之间有着怎样的关系呢?
进程
一个进程就是一个程序运行的实例。当启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码,运行中的数据和一个执行任务的主线程,这样的一个运行环境就叫进程
线程
线程不能单独存在,它是由进程来启动和管理的。线程依附于进程,进程中使用多线程并行处理能提升运算效率
两者之间的关系
1、进程中的任意一线程执行出错,都会导致整个进程的崩溃
2、线程之间可以共享数据
3、当一个进程关闭后,操作系统会回收进程所占用的内存
4、进程之间的内容相互隔离
渲染机制
从HTML、CSS和JavaScript开始
了解浏览器的渲染原理,我们就要从理解 HTML 、 CSS 和 JavaScrip 开始,我们先来看一张图
HTML (超文本标记语言),顾名思义,由标记(标签)和文本组成,每个标签都有自己的语意,浏览器会根据标签和文本展示对应的内容。
CSS (层叠样式表),由选择器和属性组成,它可以改变 HTML 的样式,比如上图中,我们改变了 span 的颜色由蓝色为绿色。
JavaScript ,我们可以通过 JS 完成很多事情,例如上图中修改样式。
下面开始分析渲染的原理
渲染流水线
渲染模块由于渲染的机制的复杂,被划分为了很多子阶段,输入的 HTML 经过这些子阶段,最后会输出为像素。这样的处理流程就叫做 渲染流水线
按照渲染的时间顺序,流水线可分为几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成
构建DOM树
由于浏览器无法直接理解和使用 HTML ,所以需要将 HTML 转换为浏览器能够理解的结构( DOM 树)
树结构示意图
DOM树的构建过程
我们来分析一下下面这段代码会构建出一棵什么样的 DOM 树
我们先将上面的代码运行,然后在浏览器控制台输入 document ,看看会有什么效果
我们一层级一层级的打开就会看到如上图的效果,我们可以根据这每一层级展开的效果,绘制出一棵 DOM 树结构,如下:
接下来,我们试一下利用 JS 修改一下内容,看有什么改变:
我们可以看到“浏览器”的文字变成了“chrome”
再来看一下 DOM 树是否有改变
我们看到在“浏览器”的位置换成了“chrome”,那么如何让 DOM 节点拥有样式?
样式计算
样式计算,顾名思义,就是 计算出 DOM 节点中每个元素的具体样式 ,这个阶段会分为三部分:
- 把 CSS 转换为浏览器能够理解的结构
- 转换样式表中的属性值,使其标准化
- 计算出 DOM 树中每个节点的样式
CSS样式来源
- link 导入外部样式资源
浏览器会新开辟一个线程,去服务器获取对应的资源文件(不阻碍主线程的渲染)
- style 内嵌样式
从上到下解析,解析完继续解析 DOM 结构。在真实项目中,如果 css 代码不是很多,或是移动端项目,我们应该使用内嵌式,以此来减少 http 资源的请求,提高页面渲染速度
- 行内样式
- @import 导入
它是同步的,不会开辟新线程去加载资源文件,而是让主线程去获取,这阻碍 DOM 结构的继续渲染;只有把外部样式导入进来,并且解析后,才会继续渲染 DOM 结构
把CSS转换为浏览器能够理解的结构
浏览器就像不能理解 HTML 一样,不理解 CSS ,所以当渲染引擎接收到 CSS 文件时,会执行转换操作,将 CSS 文本转换为浏览器可以理解的 styleSheets 结构。
在 HTML 中,在浏览器中输入 document 可以查看 html 的结构。在 css 中,可以输入 document.styleSheets 看到 css 的结构
现在的结构是空的,我们来加一些样式,看看效果
转换样式表中的属性值,使其标准化
属性值标准化就是将所有值转换为渲染引擎容易理解的、标准化的计算值。我们大致看一下效果:
- 标准化前
body {
font-size: 2em;
color: black;
font-weight: bold;
...
}
复制代码
- 标准化后
body {
font-size: 16px;
color: rgb(0, 0, 0);
font-weight: 700;
...
}
复制代码
计算出DOM树中每个节点的具体样式
样式计算有两个CSS的规则:继承规则和层叠规则
- CSS继承规则
CSS 继承就是每个 DOM 节点都包含有父节点的样式。我们来看一下下面这段代码中如何应用到 DOM 节点上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
h1 {
color: red;
}
div {
color: blue;
}
span {
font-size: 16px;
}
</style>
</head>
<body>
<h1>掘金</h1>
<div>
<span>浏览器</span>
<span>渲染原理</span>
构建DOM树
</div>
</body>
</html>
复制代码
子节点会拥有父节点的样式,由此我们可以画出这样一张图
我们还可以打开控制台,看一下选中 span 标签,都会看到哪些内容
通过上图,我们可看到一个元素的样式、继承过程等, userAgent 样式是浏览器默认的内置样式,如果我们不提供任何样式,就会使用此样式。
- 样式层叠规则
层叠在 CSS 处于核心地位,它是 CSS 的一个基本特征,它定义了如何合并来自多个源的属性值的算法。
样式计算阶段最终输出的内容是每个 DOM 节点的样式,并且保存在了 ComputedStyle 中。我们可以通过控制台看到某个 DOM 元素最终的计算样式
布局阶段
现在我们不知道 DOM 元素的几何位置信息,所以现在我们需要计算出 DOM 树中可见元素的几何位置,这个计算过程就叫做布局。布局阶段有两个过程:
- 创建布局树
- 布局计算
创建布局树
创建布局树的意思就是创建一棵只包含可见元素的树。我们来看下面一段代码创建布局树的过程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
h1 {
color: red;
}
div {
color: blue;
}
div span {
font-size: 16px;
}
div span:last-child {
display: none;
}
</style>
</head>
<body>
<h1>掘金</h1>
<div>
<span>浏览器</span>
<span>渲染原理</span>
构建DOM树
</div>
</body>
</html>
复制代码
构建布局树的过程中, DOM 树中所有不可见的节点都不会包含在这棵树中。浏览器会遍历 DOM 树中所有能看见的节点,然后把这些节点加入到布局中;不可见的节点就会被忽略, head 标签下面的内容、 div 下最后一个 span 节点都不会在布局树中,我们看一下这个过程图感受一下~
布局计算
布局计算就是计算布局树节点的坐标位置。这个计算过程极为复杂。
分层
渲染引擎会为特定的节点生成专用的图层,并生成一棵对应的图层树。这样做是因为页面中可能含有很多复杂的效果,我们可以打开控制台看一下页面的分层情况
我们可以看到,渲染引擎给页面分了很多图层,这些图层会按照一定顺序叠加在一起,形成最终的页面
那么图层的来源有哪些?
1、拥有层叠上下文属性的元素会被提升为单独的一层
层叠上下文可以使能够使 HTML 元素具有三维的概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。哪些元素具有层叠上下文属性?
2、需要剪裁的地方会被创建为图层
当我们创建一个有宽度和高度的 div 时,里面的文字内容可能会超出这个区域,这时候渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,例如
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
100px;
height: 100px;
background: yellow;
overflow: auto;
}
</style>
</head>
<body>
<div>
我们经常写`HTML`、`CSS`和`JavaScript`,写好这些之后,我们就会在浏览器中看到页面,那浏览器究竟在这背后做了一些什么事情呢?本篇文章将揭晓答案!
了解浏览器的渲染原理是我们在通往更深层次的前端开发中不可缺少的,它可以让我们从更深层次、角度去考虑性能优化等~
</div>
</body>
</html>
复制代码
运行结果
我们再打开控制台的 layers 看一下效果
可以看到渲染引擎为文字部分单独创建了一个图层。
在布局树中的节点如果拥有对应的图层,这个节点就是一个图层,如果没有,这个节点就属于父节点的图层,如下图:
图层绘制
创建好图层树后,渲染引擎会绘制图层树中的每个图层。渲染引擎会将图层绘制分解为很多小的绘制指令,然后将这些指令按照顺序组成待绘制列表,我们可以打开控制台的 layers ,选择 document 层,看一下效果
栅格化操作
栅格化就是将图块转换位位图,图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有图块的栅格化都是在线程池内执行的。
图层绘制列表准备好之后,主线程会把这个绘制列表提交给合成线程,绘制操作由渲染引擎中的合成线程来完成。
合成线程将图层划分为图块,然后合成线程会按照视口(可见区域)附近的图块优先生成位图。
合成与显示
所有的图块都被光栅化后,合成线程会生成一个绘制图块的命令( DrawQuad ),然后将该命令提交给浏览器进程。浏览器进程里面 viz 组件用来接收 DrawQuad 命令,将其页面内容绘制到内存中,最后将内存显示到屏幕。这个时候,我们就看到了页面
完善渲染流水线示意图
根据上文中描述,我们可以画出这样一张图
我还在网上找到了另外一张图
这两张图都是描述浏览器的渲染流程的。