这是我参与更文挑战的第8天,活动详情查看:更文挑战
接着上一篇运行机制里的渲染部分来探究。
我们从一个经典的问题来看:
输入url到页面生成,经历了什么?
这里主要说浏览器渲染的部分。
当浏览器的Network线程做完安全检查后,会把任务交给UI线程,UI线程所属进程——Browser进程会通过IPC告诉Renderer进程:可以干活了。
Renderer进程回复:知道了。
Browser进程马上就去更新当前会话,安排记录前进、后退等等。
接下来就看Renderer进程的了,它的主要任务就是把HTML、CSS、JavaScript渲染出一个页面来,那么它要干些什么呢?
解析(Parsing)
-
构建DOM: 通过HTML文件去构建一个DOM Tree
-
子资源加载: 加载依赖的其他css、js资源
注意,在加载js文件时,会阻塞HTML的解析过程。
样式计算(Style calculation)
主线程拿到css文件后,会根据设定的样式,来计算具体的样式。
布局(Layout)
知道了样式,但还不知道具体的位置,仍然无法正确渲染出页面。就比如小红打电话给小明说:“页面上有一个红色圆圈和蓝色正方形。”小明不知道位置,是无法精准画出来的。
这个时候就需要布局来计算出几何信息,计算的过程是:
主线程遍历DOM Tree
,根据DOM节点的计算样式计算出Layout Tree / Render Tree
。其中包括每个节点的坐标和盒子(bounding box)大小。
值得注意的是:
- 这个
Layout tree
中,只会出现visible
的节点。
display: none
的元素不会出现在Render Tree
中
visibility: hidden
会出现在Render Tree
中
- 伪元素会出现在
Render Tree
中,但不存在于DOM Tree
中,因为它是通过css布局计算出来的元素。
绘画(Paint)
知道了样式和位置,我们可以从上到下完整渲染出来了吗?不,还不可以。因为还有一个顺序问题,某种程度上说页面是立体的而非平面的,元素之间会有重叠的关系。这也是css中z-index
的作用所在。
图 : 先画圆还是先画方,这是一个问题。
这个时候,主线程会遍历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即可。