分层 现在我们有了布局树,而且每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制⻚面了? 答案依然是否定的。
因为⻚面中有很多复杂的效果,如一些复杂的3D变换、⻚面滚动,或者使用z-indexing做z轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。如果你熟悉PS,相信你会很容易理解图层的概念,正是这些图层叠加在一起构成了最终的⻚面图像。【重绘】是从绘制开始的!!
可以通过Chrome的Layers来查看分层情况。
layout tree 到 layer tree的过程
可以看到 LayoutTree 经过分层后变成了LayerTree
早期说的渲染树说的应该是LayerTree 重绘也是在LayerTree上操作的
并不是说每一个节点都是单独的一层,如果没有对应的层就隶属于父节点层,所有节点都会间接或者直接的属于一层。
要满足什么条件,才会提升为一层?
1、拥有层级属性的会被单独放一层 - position 属性为 relative、fixed、sticky、absolute 透明的(opacity 小于 1)、滤镜(filter)、遮罩(mask)、混合模式(mix-blend-mode 不为 normal)剪切路径(clip-path)2D 或 3D 转换(transform 不为 none)隐藏背面(backface-visibility: hidden)倒影(box-reflect)column-count(不为 auto)或者column-widthZ(不为 auto)对不透明度(opacity)、变换(transform)、滤镜(filter)应用动画
OverflowClipLayer 剪切溢出内容(overflow: hidden)
另外以下 DOM 元素对应的 Render Object 也会创建单独的 Render Layer: Document HTML Canvas Video
详细可以看 developer.mozilla.org/zh-CN/docs/… 这个文章
有一种隐士的转换
p{height:10px;overflow:auto}
<p>
1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />1<br />
</p>
如果超过了10px这时候就发生了裁剪
显示内容的部分会单独创建一个层用来显示内容
如果发生了滚动,滚动的部分也会创建一个单独的层。
元素有了层叠上下文的属性或者需要被剪裁,满足这任意一点,就会被提升单独的一层。
绘制图层 在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是怎么实现图层绘制的,可以想象成画画,先画背景再画场景等等一层层的从下到上的去丰富画板。 每一层对应的指令集,指令集会有很多的绘制指令。然后按照指令一点点绘制就行了。
从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
点开layers 查看document 可以看到每一层的绘制指令
1、绘制指令 2、绘制的过程
合成线程操作 绘制列表只是存储了绘制的指令并没有真正的去画,真正的处理在渲染进程中的合成线程中来操作。
可以看到所有的绘制指令全部都交付给了合成线程了。
屏幕上的可见区域叫做viewport
通常一个页面会比较的大,只能显示这个页面的一部分,所以窗口之外的就没有必要先来显示,优先级比较高的是viewport的内容,
合成线程会将图层划分为图块(tile) 这些图块的大小通常是256x256或者512x512
layer被切成了多个tile的块 在viewport 优先显示这些块。
合成线程会将图层划分为图块后开始要对这些块进行删格化 也就是光栅的流程 生成位图,如果对PS了解对删格化应该不陌生。图块是栅格化执行的最小单位,渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中
GPU操作是运行在GPU进程中,如果栅格化操作使用了GPU,那么最终生成位图的操作是在GPU中完成的,这就涉及到了跨进程操作
删格化线程池的线程通过ipc 调用 GPU进程进行快速删格化
渲染进程把生成图块的指令发送给GPU,然后在GPU中执行生成图块的位图,并保存在GPU的内存中
合成和显示
一旦所有的图块都删格化完成,合成线程就会生成一条DrawQuad 的指令 发送给主进程,主进程里面有个叫VIZ的组件用来接收DrawQuad指令,从显存调用,将其⻚面内容绘制到内存中,在屏幕上展示。
结合上图,一个完整的渲染流程大致可总结为如下:
1. 渲染进程将HTML内容转换为能够读懂的DOM树DOM树结构。
2. 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheetsstyleSheets,计算出DOM节点的样式。
3. 创建布局树布局树,并计算元素的布局信息。
4. 对布局树进行分层,并生成分层树分层树。
5. 为每个图层生成绘制列表绘制列表,并将其提交到合成线程。
6. 合成线程将图层分成图块图块,并在光栅化线程池光栅化线程池中将图块转换成位图。
7. 合成线程发送绘制图块命令DrawQuadDrawQuad给浏览器进程。
8. 浏览器进程根据DrawQuad消息生成⻚面生成⻚面,并显示显示到显示器上。
重排”“重绘”和“合成”
1.更新了元素的几何属性(重排)
从上图可以看出,如果你通过JavaScript或者CSS修改元素的几何位置属性,例如改变元素的宽度、高度
等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排重排。无疑,重排需要更新完整重排需要更新完整 的渲染流水线,所以开销也是最大的的渲染流水线,所以开销也是最大的。
2.更新元素的绘制属性(重绘)
从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘重绘。相较于重排操作,重重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些
3.合成阶段 如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成合成。具体流程参考下图
我们使用了CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率相对于重绘和重排,合成 关于合成层和层可以看这篇文章 juejin.cn/post/704700…
如何减少重排和重绘? 1、使用class操作样式,而不是频繁操作style 2、避免使用table布局 3、批量dom操作,例如createDocumentFragment,或者使用框架,例如React 4、对dom属性的读写要分离 5、will-change:transform做优化 6、现代浏览器做了优化,把滚动操作交给了合成线程来处理,也就是说滚动的内容会被当成一个单独的图层,发生滚动的事件的时候,图层直接由合成线程来生成,也就是说这种情况下没有占用主线程,所以通常情况下不会产生重排和重回操作,只是简单合成就可以了,这样效率是最高的
为什么说“通常”呢?这是因为目前渲染流程还是很复杂的,在滚动⻚面时,有些情况下,如果合成线
程搞不定的,那么还要交给主线程去处理,这时候就涉及到重拍了,不过技术是往前发展的,渲染流程会变得越来约简单高效!