HTML、CSS 和 JavaScript 的含义
- HTML 的内容是由标记和文本组成。标记也称为标签,每个标签都有它自己的语义,浏览器会根据标签的语义来正确展示 HTML 内容。
- CSS 又称为层叠样式表,是由选择器和属性组成,如果需要改变 HTML 的字体颜色、大小等信息,就需要用到 CSS。
- JavaScript(简称为 JS),使用它可以使网页的内容“动”起来,比如上图中,可以通过 JavaScript 来修改 CSS 样式值,从而达到修改文本颜色的目的。
渲染流程
由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。
- 构建 DOM 树(DOM)
浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。
<html>
<head>
<link rel="stylesheet" href="./demo.css" />
</head>
<body>
<p><span>重点介绍</span> 渲染流程</p>
<div>
<p>green</p>
<div>red</div>
</div>
</body>
</html>
上段的代码构建dom树过程
构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM。
在控制台里面输入“document”后回车,就能看到一个完整的 DOM 树结构
DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。
document.getElementsByTagName("p")[0].innerText = "black"
- 样式计算(Recalculate Style)(Style)
样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成。
1)把 CSS 转换为浏览器能够理解的结构
CSS 样式来源主要有三种:
通过 link 引用的外部 CSS 文件
《style》标记内的 CSS元素的 style
属性内嵌的 CSS
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
可以在 Chrome 控制台中查看其结构,只需要在控制台中输入 document.styleSheets
渲染引擎会把获取到的 CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。
2)转换样式表中的属性值,使其标准化
现在已经把现有的 CSS 文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
上面的 CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。
ps:Px表示“绝对尺寸”,em表示相对尺寸。
1em=父元素的fontSize的px
1 ÷ 父元素的font-size × 需要转换的像素值 = em值
标准化后的属性值见下图
3)计算出 DOM 树中每个节点的具体样式
涉及到 CSS 的继承规则和层叠规则
- CSS 继承 (CSS 继承就是每个 DOM 节点都包含有父节点的样式。)
- 层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。详见CSS层叠
F12都能看到 继承、层叠样式 和 最终样式
3. 布局阶段(Layout)
现在,有 DOM 树和 DOM 树中元素的样式了,但这还不足以显示页面,因为还不知道 DOM 元素的几何位置信息。那么接下来就需要计算出 DOM 树中可见元素的几何位置,这个计算过程叫做布局。
Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。1. 创建布局树你可能注意到了 DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。2.布局计算。
3. 创建布局树
DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
4. 布局计算
创建完布局树后有了一棵完整的布局树后计算布局树节点的坐标位置。
浏览器大体上完成了下面这些工作:
1.遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
2.不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。
3 计算布局树节点的坐标位置
pps:visibility: hidden的元素会出现在布局树中,这与display: none是不同的
到现在的流程是在 HTML 页面内容被提交给渲染引擎之后,渲染引擎首先将 HTML 解析为浏览器可以理解的 DOM;然后根据 CSS 样式表,计算出 DOM 树所有节点的样式;接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中。
4. 分层(layout layer)
页面中可能有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
可以打开 Chrome 的“开发者工具”,选择“Layers”标签,就可以可视化页面的分层情况(看京东主页)
浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如上图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。
1)拥有层叠上下文属性的元素会被提升为单独的一层
详见 css层叠
2)需要剪裁(clip)的地方也会被创建为图层
<style>
div {
width: 200;
height: 200;
overflow: auto;
background: red;
}
</style>
<body>
<div>
<p>1111111111111111111111111111111111111</p>
<p>2222222222222222222222222222222222222222222222222222222222</p>
<p>333333333333333333333333</p>
</div>
</body>
5.图层绘制(paint)
我们拿笔画画,背景蓝,中间一个红圆,红圆里套一绿三角。
图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。
“开发者工具”可实际体验下绘制列表,点击具体图层
6.栅格化(raster)操作
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程
通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)。基于这个原因,合成线程会将图层划分为图块(tile)。
然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的
7.合成和显示
图块都被栅格化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
渲染完了
渲染总结
渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
创建布局树,并计算元素的布局信息。对布局树进行分层,并生成分层树。
为每个图层生成绘制列表,并将其提交到合成线程。
合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
关于重排、重绘、合成
- 更新了元素的几何属性(重排)
通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。 - 更新元素的绘制属性(重绘)
如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。 - 直接合成阶段
如果更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。
使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。