浏览器探究之渲染流程(3)

459 阅读5分钟

这是我参与更文挑战的第8天,活动详情查看:更文挑战


接着上一篇运行机制里的渲染部分来探究。

我们从一个经典的问题来看:

输入url到页面生成,经历了什么?

image.png

这里主要说浏览器渲染的部分。

当浏览器的Network线程做完安全检查后,会把任务交给UI线程,UI线程所属进程——Browser进程会通过IPC告诉Renderer进程:可以干活了。

Renderer进程回复:知道了。

Browser进程马上就去更新当前会话,安排记录前进、后退等等。

image.png

接下来就看Renderer进程的了,它的主要任务就是把HTML、CSS、JavaScript渲染出一个页面来,那么它要干些什么呢? image.png

解析(Parsing)

  1. 构建DOM: 通过HTML文件去构建一个DOM Tree

  2. 子资源加载: 加载依赖的其他css、js资源 注意,在加载js文件时,会阻塞HTML的解析过程。

样式计算(Style calculation)

主线程拿到css文件后,会根据设定的样式,来计算具体的样式。

布局(Layout)

知道了样式,但还不知道具体的位置,仍然无法正确渲染出页面。就比如小红打电话给小明说:“页面上有一个红色圆圈和蓝色正方形。”小明不知道位置,是无法精准画出来的。

image.png

这个时候就需要布局来计算出几何信息,计算的过程是:

主线程遍历DOM Tree,根据DOM节点的计算样式计算出Layout Tree / Render Tree。其中包括每个节点的坐标和盒子(bounding box)大小。

值得注意的是:

  1. 这个Layout tree中,只会出现visible的节点。

display: none 的元素不会出现在Render Tree
visibility: hidden 会出现在Render Tree

  1. 伪元素会出现在Render Tree中,但不存在于DOM Tree中,因为它是通过css布局计算出来的元素。

绘画(Paint)

知道了样式和位置,我们可以从上到下完整渲染出来了吗?不,还不可以。因为还有一个顺序问题,某种程度上说页面是立体的而非平面的,元素之间会有重叠的关系。这也是css中z-index的作用所在。

image.png

图 : 先画圆还是先画方,这是一个问题。

这个时候,主线程会遍历Render Tree,拿到一份步骤记录,告诉我们先画什么,再画什么。

比较复杂的情况是,这边刚画到下面,突然上面的一个元素变小了,一切只能重新来过。有时候一个简单的css动画就足以让这个过程没完没了。浏览器辛苦点当然也没什么,但是如果重新渲染一次的速度,跑不过一个渲染帧(浏览器通常是1分钟60帧),那就会让人看出卡顿了。

合成(Compositing)

现在一切准备就绪了,要把页面转换成屏幕上的像素了,这个过程的专业名词叫光栅化(Rasterizing)。

我们把所有的东西合在一起,从上到下逐个像素地光栅化,一切正常。但是当我们滚动一下屏幕,或者处理一下动画,就得全部重新光栅化。

于是浏览器又采用了新的方法,把元素分层(Layer),按照层的概念来光栅化,然后再统一合成(Compositing)。

在 Chrome 中,有几种不同的层类型:

渲染层 RenderLayers

渲染层也是最基础的分层,同一个Z轴空间的元素,都归为同一层。

图形层 GraphicsLayer

它并不直接处理渲染层,而是处理合成层,并且交由GPU来处理。

合成层 CompositingLayer

一些特殊的渲染层,会被浏览器自动提升为合成层。合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 的父层共用一个。

合成层的优点
  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快;
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint
合成层创建

在Chrome中,满足以下条件的元素就可以获得单独创建的合成层:

  • 3D 或透视变换(perspective transform) CSS 属性: translate3d、translateZ
  • video、canvas、iframe 等元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

因此,有时候碰到某个渲染元素对CPU的消耗过高,可以通过添加属性will-change: transform transform: translateZ(0)等来提升为一个合成层,开启GPU加速。当然,不宜多用,用多了就形成了合成层的内卷。

回流(Reflow)和重绘(Repaint)

最后再说两个常常被提到的概念,回流和重绘。最早看面试题的时候流行背一句:「回流一定会重绘,重绘不一定会回流。」感觉跟绕口令似的,而且二者还有各种不同的中文翻译,一不小心可能还弄混了。

其实理解完整个渲染过程,这两个概念自然就理解了。

回流(Reflow)就是再次回到Layout阶段,只有影响了布局的变化,比如位置、大小等等,才会回到这个阶段重新计算、布局,之后交给它的下家去Paint。

重绘(Repaint)就是再次回到Paint阶段,不影响位置的style变化都只需要触发Paint即可。