前不久写了一篇关于如何使用 Chrome DevTools 优化高德地图动画的文章,其中提到了 composite,可是并无细谈。思考许久,仍是以为有必要再总结一下。
css
通俗来讲:在 DOM 树中每一个节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,就会造成一个 RenderLayers ,也就是渲染层。
RenderLayers 来保证页面元素以正确的顺序合成,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。node
一旦加载并解析页面,它就在浏览器中做为许多Web开发人员熟悉的结构来表示:DOM。然而,当呈现一个页面时,浏览器有一系列不直接暴露给开发者的中间表示。这些结构中最重要的是层。git
到这里,层的概念是有了。composite 翻译过来就是咱们常说的合成,那么他是怎么工做的?github
先贴出一张流程图,稍后咱们细说:
在 Chrome 中其实有几种不一样的层类型:web
提到 RenderLayers 不得不说 RenderObjects :
RenderObjects 保持了树结构,一个 RenderObjects 知道如何绘制一个 node 的内容, 他经过向一个绘图上下文(GraphicsContext)发出必要的绘制调用来绘制 nodes。chrome
每一个 GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,做为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,而后 draw 到屏幕上,此时,咱们的页面也就展示到了屏幕上。
编程
GraphicsContext 绘图上下文的责任就是向屏幕进行像素绘制(这个过程是先把像素级的数据写入位图中,而后再显示到显示器),在chrome里,绘图上下文是包裹了的 Skia(chrome 本身的 2d 图形绘制库)canvas
对于隐式合成,CSS GPU Animation 中是这么描述的:
浏览器
This is called implicit compositing: One or more non-composited elements that should appear above a composited one in the stacking order are promoted to composite layers — i.e. painted to separate images that are then sent to the GPU.缓存
咱们先来看下面的图示:
假设一种场景,咱们须要 A 显示在 B 之上,而后为 B 添加移动的动画,这里就会出现一个逻辑问题:B 由于有动画,被提高到了合成层,最终在 GPU 上合成了屏幕图像,而 A 须要显示在 B 之上,咱们并无作任何处理。因此为了使 A 和 B 正常显示,咱们须要设置 z-index ,这时浏览器将强制提高 A 为复合层,随后进行 repaint。
这时候,composite 隐式合成就出现了。固然,还有更多的场景,咱们继续。
咱们知道,在某些特定条件下,浏览器会主动将渲染层提至合成层,那么影响 composite 的因素有哪些?
这里只举出部分例子,无线性能优化:Composite这篇文章描述的很详细,这里就不赘述了。
1.层压缩:
相似咱们举出的层隐式合成的例子,可能简单的重叠就会产生大量的合成层,这样会占用不少无辜的 CPU 和 内存资源,严重影响了页面的性能。这一点浏览器也考虑到了,所以就有了层压缩(Layer Squashing)的处理。
浏览器的自动的层压缩也不是万能的,有不少特定状况下,浏览器是没法进行层压缩的。
此处摘录自无线性能优化:Composite,demo 请查看原文。
若是多个渲染层同一个合成层重叠时,这些渲染层会被压缩到一个 GraphicsLayer 中,以防止因为重叠缘由致使可能出现的“层爆炸”。
2.层爆炸:
经过以前的介绍,咱们知道同合成层重叠也会使元素提高为合成层,虽然有浏览器的层压缩机制,可是也有不少没法进行压缩的状况。也就是说除了咱们显式的声明的合成层,还可能因为重叠缘由不经意间产生一些不在预期的合成层,极端一点可能会产生大量的额外合成层,出现层爆炸的现象。
解决层爆炸的问题,最佳方案是打破 overlap 的条件,也就是说让其余元素不要和合成层元素重叠,譬如巧妙的使用 z-index 属性。
上面提到了层合成的过程会产生内存消耗,那么咱们如何来评估层消耗的内存,下面举例来讲明:
<!-- jartto test -->
<div id="a"></div>
<div id="b"></div>复制代码
#a, #b {
will-change: transform;
}
#a {
width: 100px;
height: 100px;
background: rgb(255, 0, 0);
}
#b {
width: 10px;
height: 10px;
background: rgb(255, 0, 0);
transform: scale(10);
}复制代码
如上,咱们建立了两个容器 #a 和 #b,#a 的物理尺寸是 100×100px(100×100×3 = 30000 字节),而 #b 只有10×10px(10×10×3 = 300 字节)但放大了 10 倍。#b 因为存在 will-change 属性,transform 动画将经过 GPU 来渲染图层。
咱们经过图像的高度乘以图像的宽度来得到图像中像素的数量。而后,咱们将其乘以3,由于每一个像素都用三个字节(RGB)描述。那么不难理解,若是图像包含透明区域,咱们要乘以4,由于须要额外的字节来描述透明度:(RGBA):100×100×4 = 40000 字节。
从上面的例子中,咱们得出了一个很是有意义的结论,从而帮助咱们去作一些简单的优化。例如:若是你想要为一张大图添加动画,你能够先下载缩小版(原版 10% )而后放大显示。这对用户是无感知的,可是咱们却精简了页面加载,从而提高了用户体验。
很好,咱们能够经过上述来评估内存消耗了,这里引出了两个术语 Reflow 和 Repaint ,简单温习一下:
Reflow要比Repaint更花费时间,也就更影响性能。因此在写代码的时候,要尽可能避免过多的Reflow。
reflow 的缘由:
减小 reflow / repaint
既然整个渲染过程如此耗时,那么大多数人喜欢使用 translateZ(0) 与 will-change 开启硬件加速的行为就很容易理解了。
转了一圈,又回到了本文的重点:合成层。提高合成层的最好方式是使用 CSS 的 will-change 属性。而 will-change 设置为 opacity、transform、top、left、bottom、right 能够将元素提高为合成层。
先来看看 will-change 的浏览器支持状况,点击查看
整体支持状况还不错,因此咱们能够像下面这样使用:
#jartto {
will-change: transform;
}复制代码
固然,对于个别不支持的浏览器,咱们使用 translateZ(0) 来解决,点击查看
#jartto {
transform: translateZ(0);
}复制代码
We already know that animation of transform and opacity via CSS transitions or animations automatically creates a compositing layer and works on the GPU.
那么问题来了,硬件加速依赖 GPU ,而 GPU 为何会比 CPU 快,咱们接着来看。
文中反复提到了 CPU 和 GPU ,相信不少童鞋可能会产生这样的疑惑:为何要开启硬件加速,以及 GPU 优点到底在哪里?
咱们先简单的了解一下 GPU 的工做原理,GPU 处理数据的过程大概是这样的:
从 pu-accelerated-compositing-in-chrome 这篇文章能够看出,硬件合成的好处有三种:
组成缓存元素的图像会更快,而这正是 GPU 的强势之处:它可以很快地用亚像素精度合成图像,这给动画增长了显著的平滑度。
咱们能够这么理解,GPU 是一个单独的计算机:每个现代设备的一个重要部分其实是一个独立的单元,有本身的处理器和本身的内存和数据处理模型。与其余应用程序或游戏同样,浏览器必须像外部设备那样与 GPU 对话。
更多 GPU 的介绍,请看这里传送门,这里就不扯远了。
这里我再补充一点:
咱们能够说 CPU 所作的工做都在软件层面,而 GPU 在硬件层面,咱们能够用软件(使用 CPU )作任何事情,可是对于图像处理,一般用硬件会更快,由于GPU使用图像对高度并行浮点运算作了优化。
这也是我以前一直困扰的地方,咱们一味的强调硬件加速,而忽略了 CPU 自己的做用。大体过程可能以下:
咱们看到了 GPU 确实很强势,可是咱们最好不要把全部东西一古脑儿抛给 GPU ,问题在于 GPU 并无无限制处理性能,并且一旦资源用完的话,性能就会开始降低了(即便 CPU 并无彻底占用)。事实上他们有本身的职责,各司其职,各尽其才,才能发挥出更大的做用。
咱们不打无准备的仗,合理的利用工具,才能大大提升编程效率。这里自荐一篇文章优化高德地图动画,内容包括:
原谅我标题党了,起初是为了找寻优化高德地图动画的方案,结果写成了实际的例子,一步步介绍了 Chrome DevTools 。文章已经发表,因此就没在去更改题目,若是能够的话,我但愿的题目是:动画优化之如何使用 Chrome DevTools
提高为合成层简单说来有如下几点好处:
若是你已经把一个元素放到一个新的合成层里,那么可使用 Timeline 来确认这么作是否真的改进了渲染性能。别盲目提高合成层,必定要分析其实际性能表现。
实际上,在内存资源有限的设备上,合成层带来的性能改善,可能远远赶不上过多合成层开销给页面性能带来的负面影响。同时,因为每一个渲染层的纹理都须要上传到 GPU 处理,所以咱们还须要考虑 CPU 和 GPU 之间的带宽问题、以及有多大内存供 GPU 处理这些纹理的问题。
因此,你就会明白,这里咱们使用方式二而不使用方式一的缘由了:
/*jartto:方式一*/
@keyframes move {
from { left: 30px; }
to { left: 100px; }
}复制代码
/*jartto:方式二*/
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(70px); }
}复制代码
优化实际上是一个过程,咱们须要一个点一个点的处理、突破。没有什么是一蹴而就的,更没有所谓的银弹。就像高中物理书中所说的偏差:“偏差是不可避免的,只能减小”。
优化也同样,咱们只能尽力去作,而不能强求。不断尝试,方是永恒。
参考:
无线性能优化:Composite
CSS GPU Animation
web优化之composite
浏览器渲染
gpu-accelerated-compositing-in-chrome
视图渲染、CPU和GPU卡顿缘由及其优化方案