渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

渲染流水线

渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 流水线分为以下子阶段:

  • 构建DOM树
  • 样式计算
  • 布局阶段
  • 分层
  • 绘制
  • 分块
  • 光栅化
  • 合成

接下来会从以下三个方面讲解每个子阶段:

  • 输入的内容
  • 处理过程
  • 输出内容

渲染过程

构建DOM树

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

构建DOM树的流程: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

总结:

  • 输入内容: HTML。
  • 处理过程: HTML解析器解析。
  • 输出内容: DOM树。

HTML和DOM看起来内容一样,那么它们的不同在哪里?

DOM 是保存在内存中的树状结构,可以通过JavaScript来查询或修改内容。

虽然构建完了DOM树,但是我们还不知道每个节点的样式,那么这就需要进行样式计算了。

样式计算

这里的样式计算也是经历了三个阶段:

1、把CSS转换为浏览器能够理解的结构

CSS的样式来源有哪些?我们通过一张图就可以很清晰的看到了: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 和HTML文件一样,浏览器也是无法直接理解CSS样式的,需要将其转换为浏览器可以理解的结构——styleSheets。

渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

CSS样式转换为styleSheets以后,就具备了查询和修改样式的功能。

2、转换样式表中的属性值,使其标准化

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

以上代码中的em、blue、bold等是不被浏览器理解的,需要将其转换为浏览器引擎容易理解的、标准化的计算值,这个过程就是标准化。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

3、计算出每个DOM节点的样式

遵循CSS的继承原则层叠原则。 关于继承原则和层叠原则,我们来做两个好玩的小题目:

  • AAA 所在标签P和 BBB所在标签P的距离是多少?
<style>     p {         font-size: 16px;         line-height: 1;         margin-bottom: 15px;         margin-top: 10px;     } </style> <p>AAA</p> <p></p> <p></p> <p></p> <p>BBB</p> 复制代码

答案: 15px; margin纵向重叠的原则:相邻元素的margin-top和margin-bottom会重叠;空白内容的元素也会重叠。

  • P标签的行高是多少?
<style>     body {         font-size: 18px;         line-height: 200%;     }          p {         font-size: 16px;     } </style> <body>     <p>Good</p> </body> 复制代码

答案:36px; line-height的继承原则:

  • 具体数值,比如24px, 直接继承该数值。
  • 具体的比例,比如1.5, 直接继承该比例。
  • 百分比,比如200%, 继承的是计算后的值。

布局阶段

有了DOM树和样式还不够,我们还需要知道DOM元素的几何位置信息,我们就需要计算出DOM树中的几何位置,这个过程就是布局。我们又将布局分为两个阶段:

1、构建布局树

因为DOM树中含有很多不可见的元素,所以我们需要另外构建一棵只包含可见元素的布局树。 如图: 一棵布局树的构建过程。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

2、布局计算

布局计算较复杂,这里不做详细描述。

分层

由于页面中会有一些复杂的3D变换、页面滚动、或者使用z-index进行z轴排序等,为了更加方便地实现这些效果,渲染引擎需要为一些特点的节点生成专用的图层,并生成一棵对应的图层树。 布局树和图层树的关系示意图: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 并不是每个节点都包含一个图层,如果一个节点没有对应的层,那么它就从属于父节点的图层。 那么什么情况下,渲染引擎才会为特点的节点创建新的图层呢?

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

以下是永远层叠上下文属性的示意图,如需知道更多的层叠上下文属性,可以参考MDN渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

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

比如一断文字内容超出了div的显示区域,那么这一块显示区域又会被提升出来成为单独一个图层,如果又出现了滚动条,那么这个滚动条也会被提升出来成为单独的一层。 示意图如下: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

图层绘制

在完成图层树的构建后,我们要对图层进行绘制,那么渲染引擎是怎么实现图层绘制的呢? 如果你要在一个画布上,画一个蓝色背景下的绿色圆和红色三角形,你要怎么画?

  • 绘制蓝色背景
  • 绘制一个绿色的圆
  • 绘制一个红色三角形

渲染引擎的图层绘制也是与之类似的,会把一个图层的绘制拆分成很多小的绘制指令,然后按照顺序组成一个待绘制的列表。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

图层绘制这个阶段还不是真正的绘制,它只是形成绘制列表,而真正的绘制操作实际上是在合成线程中完成的,让我们来看真正的绘制阶段——栅格化。

栅格化操作

真正的绘制操作是在合成线程中完成的,当我们绘制列表生成后,主线程会向合成线程提交绘制列表,由合成线程开始绘制。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 那么在合成线程里,又是如何工作的呢? 首先,合成线程会将图层分成一个个小的图块。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 然后,合成线程会根据视口附近的图块来优先生成位图,这个将图块生成位图的操作,就叫栅格化。栅格化的操作一般是在一个单独的线程池里操作的,示意图如下: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 栅格化的时候会借助GPU进行栅格化,因为这样可以加速生成位图。生成的位图被保存在GPU的内存中。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

合成显示

现在所有的栅格化操作完成了,合成线程就会生成一个绘制的命令———“DrawQuad”,合成线程将该命令提交给浏览器进程,浏览器进程收到该命令后,将页面内容绘制到内存中,最后将内存中的页面内容展示到屏幕中。

总结

用一张完整的渲染流水线图,可以很好的总结以上的一系列流程了。 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

引申和思考

重排

如图: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 如果你通过CSS或JS改变了元素的高度、宽度等几何位置属性,那么浏览器会触发重新布局,以及解析接下来的一系列子阶段,这个过程就是重排。重排需要更新完整的流水线,所以开销最大。

重绘

如图: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 如果你是改变了元素的背景色,不是元素的几何位置属性,进入绘制及以后的子阶段,这就是重绘,会直接跳过布局和分层的阶段,,所以执行效率会比重排要高。

合成

如图: 渲染流程:HTML、CSS和JavaScript,是如何变成页面的? 比如上图,我们通过transform来实现动画,可以跳过布局和绘制阶段,直接进入合成线程中的阶段,这就是合成。这种效率是最高的,避开了重排和重绘,也没有占用主线程的资源,所以相对于重排和重绘,合成能大大提升绘制效率

思考

减少重绘、重排的办法有哪些? 可以参考这篇文章

参考文献

  • [1] 极客时间的《浏览器工作原理与实践》
  • [2] MDN
  • [3] 掘金的《重排和重绘》
© 版权声明
THE END
喜欢就支持一下吧
点赞108
分享
评论 抢沙发