display none的元素重新展示如何撑开页面_前端终极提问:输入URL到页面展示,发生了什么(2)--渲染流程

chrome浏览器的多进程架构

前端终极提问:输入URL到页面展示,发生了什么(1)--导航

页面的渲染机制是非常复杂,从输入HTML、CSS、JavaScript 等文件,到浏览器就会显示出完整的页面,是要经过多个复杂的过程的。一般来说要经过以下几个阶段 建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

构建 DOM 树

浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。

0812f2093cc969547062b540b67b4390.png
DOM 树的构建过程

样式计算(Recalculate Style)

样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式。

这个阶段可分为三步来完成。

1、将 CSS 文本转换为 styleSheets

和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当向渲染引擎接输入 CSS 文本后,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。

CSS 样式来源主要有三种:

  • 通过 link 引用的外部 CSS 文件
  • <style>标记内的 CSS
  • 元素的 style 属性内嵌的 CSS

在 Chrome 控制台中输入 document.styleSheets,然后就看到document.styleSheets的结构

fb448eb0fc3a1b92d866155a29eb990b.png

这个样式表包含了各种来源的css文件生成的styleSheets。

2、标准化styleSheets中的属性值

body { font-size: 2em }
p {color:blue;}
span  {display: none}
div {font-weight: bold}
div  p {color:green;}
div {color:red; }

上面的 CSS 文本中有很多属性值,如 2em、red、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值。

c4b458d1e42bab8da1b35860bb4b237a.png
准化后的属性值

3、计算出 DOM 树中每个节点的具体样式

这涉及 CSS 的继承层叠了。

继承就是每个 DOM 节点都包含有父节点的可继承样式(如 font-size , color 是可以继承的)。

层叠 定义了如何合并来自多个源的样式的算法,也就是css样式的优先级。(这个可以参考网上的其他文章)

CSS 7 种基础的选择器:
ID 选择器, 如 #id{}
类选择器, 如 .class{}
属性选择器, 如 a[href="segmentfault.com"]{}
伪类选择器, 如 :hover{}
伪元素选择器, 如 ::before{}
标签选择器, 如 span{}
通配选择器, 如 *{}

b2ad8cf014777418b633319c968e6612.png

通过上图可以很清晰的看到:元素的样式的继承过程(区域2) 和 样式的来源 (区域3)。

DOM 元素最终的计算样式,可以打开 Chrome 的“开发者工具” 查看。

8f5e580e7c2d308265d9a9e40d825ad9.png

布局阶段

现在有 DOM 树DOM 树中元素的样式,那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局

布局可以分为两步:创建布局树布局计算

创建布局树

DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树

c01bd93642458d4ceba4510fca1c0098.png
布局树的构造过程

生成的布局树中只包含可见的节点。

布局计算

布局计算,就是计算布局树中每个元素的几何坐标位置,并把计算结果保存在布局树中。

分层

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。这类似于PS中的图层概念,正是这些图层叠加在一起构成了最终的页面图像。

6fe9401e0f4c285316264fd5c9312b20.png

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如上图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。

通常满足下面两点中任意一点的元素,渲染引擎才会为它创建新的层。

1、拥有层叠上下文属性的元素会被提升为单独的一层

5edd7e50f3309e4f94ef1e8730780852.png

层叠上下文由满足以下任意一个条件的元素形成:

  • 文档根元素(<html>);
  • position 值为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素;
  • position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持);
  • flex (flexbox) 容器的子元素,且 z-index 值不为 auto
  • grid (grid) 容器的子元素,且 z-index 值不为 auto
  • opacity 属性值小于 1 的元素;
  • mix-blend-mode 属性值不为 normal 的元素;
  • 以下任意属性值不为 none 的元素:
    • transform
    • filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border
  • isolation 属性值为 isolate 的元素;
  • -webkit-overflow-scrolling 属性值为 touch 的元素;
  • will-change 值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素(参考这篇文章);
  • contain 属性值为 layoutpaint 或包含它们其中之一的合成值(比如 contain: strictcontain: content)的元素。

在层叠上下文中,子元素同样也按照上面解释的规则进行层叠。 重要的是,其子级层叠上下文的 z-index 值只在父级中才有意义。子级层叠上下文被自动视为父级层叠上下文的一个独立单元。

2、需要剪裁(clip)的地方也会被创建为图层。

<style>
      div {
            width: 200;
            height: 200;
            overflow:auto;
            background: gray;
        } 
</style>
<body>
    <div >
        <p> 所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
        <p> 从上图我们可以看到,document 层上有 A 和 B 层,而 B 层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
        <p> 图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p> 
    </div>
</body>

在这里我们把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,下图是运行时的分层结果:

877834b573fc3d71d69a3e289ca3af92.png

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

e1dbbd9700f4083bd9d3080ae67582c8.png

绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表

打开“开发者工具”的“Layers”标签,选择“document”层,就可以看到绘制列表。

c41cbb283b2694cc4d94be81dfc1d672.png

栅格化(raster)操作

图层绘制这个过程输出的是绘制列表,绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系:

8f7d6c229ab82d7c50238ff2e8ef0e23.png

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做 视口(viewport)。

fdc282d26926c664dd99df0e96b5d26f.png

基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,如下图所示:

48aeeee929f21d279a018284144399fd.png

然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:

85e353cd27333b77a40709093b67c677.png

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

相信你还记得,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:

f4094c1daf4427601305ce5687845e8e.png

渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。


渲染流水线大总结

好了,我们现在已经分析完了整个渲染流程,从 HTML 到 DOM、样式计算、布局、图层、绘制、栅格化、合成和显示。下面我用一张图来总结下这整个渲染流程:

6e3929850676ecec536bd0ae0f7f73a2.png

一个完整的渲染流程大致可总结为如下:

  1. 渲染进程将 HTML 内容转换为能够读懂的DOM 树结构。(生成dom树)
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。(样式计算)
  3. 创建布局树,并计算元素的布局信息。(布局)
  4. 对布局树进行分层,并生成分层树。(分层)
  5. 为每个图层生成绘制列表,并将其提交到合成线程。(图层绘制)
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。(栅格化)
  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程。 (合成和显示)
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。 (合成和显示)

在了解了渲染流程后,我们再来看看三个和渲染相关的概念——“重排”“重绘”和“合成”

重排

45eb00e68d79fc59922d2535f2aa1929.png

通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的

重绘

4091d8c9e3367d7f6a79b204df3e5982.png

如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些

3. 直接合成阶段

那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。具体流程参考下图:

00bbb6211157e2d70f373d219a6144a4.png

在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率

摘抄自:HTML、CSS和JavaScript,是如何变成页面的?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值