前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CSS动画与GPU

CSS动画与GPU

作者头像
ayqy贾杰
发布2023-03-15 18:49:06
2.4K0
发布2023-03-15 18:49:06
举报
文章被收录于专栏:黯羽轻扬

写在前面

满世界的动画性能优化技巧,例如:

  • 只允许改变transformopacity,其它属性不要动,避免重新计算布局(reflow)
  • 对动画元素应用transform: translate3d(0, 0, 0)will-change: transform等,开启硬件加速
  • 动画元素尽量用fixedabsolute定位方式,避免reflow
  • 对动画元素应用高一点的z-index,减少复合层数量
  • 。。。其它可能有用的规则

那么问题是:已经小心遵守这些规则了,为什么动画还会卡顿、跳帧?还能优化吗?要从哪里入手?

一.硬件加速是非规范的

The most important thing I’d like to tell you before we dive deep into GPU compositing is this: It’s a giant hack. You won’t find anything (at least for now) in the W3C‘s specifications about how compositing works, about how to explicitly put an element on a compositing layer or even about compositing itself. It’s just an optimization that the browser applies to perform certain tasks and that each browser vendor implements in its own way.

很多情况下,开启硬件加速确实能带来明显的性能提升,但是,这部分内容是非规范的,W3C并没有相关规范说明其中细节,所以通过一些技巧(例如transform: translate3d(0, 0, 0))开启硬件加速是规范之外的行为,可能得到性能提升,也可能带来严重的性能问题

也许在将来会形成规范,依照规范去做肯定能获得性能提升,但在这之前,除了必须遵从各项性能优化原则外,还要考虑实际渲染流程,从原理上解决性能问题

硬件加速(Hardware Acceleration)

硬件加速在CSS动画上是指GPU合成(GPU compositing),浏览器不直接通过CPU生成图像数据显示出来,而是把相关层数据发送给GPU,而GPU在图像数据运算方面有天生优势,所以算是加速

那么当硬件加速不可用时,浏览器怎样渲染页面?

在没有硬件加速的情况下,浏览器通常是依赖于CPU来渲染生成网页的内容,大致的做法是遍历这些层,然后按照顺序把这些层的内容依次绘制在一个内部存储空间上(例如bitmap),最后把这个内部表示显示出来,这种做法就是软件渲染(software rendering)

二.transform和opacity的特殊性

以前通过改变布局相关属性形成动画,例如:

代码语言:javascript
复制
@keyframes move {
  from { left: 30px; }
  to { left: 100px; }
}

对于动画的每一帧,浏览器都要重新计算元素的形状位置(reflow),把新状态渲染出来(repaint),再显示到屏幕上

整页reflow和repaint想想就觉得很慢,那么如果把动画元素抽出来作为前景,每帧其它部分作为背景不变,只重新渲染动画元素,再把前景背景合成起来,是不是会更快?当然会,因为GPU能快速地进行亚像素级图层合成

但是这样做的前提是能够按照动的,不动的划分出前景背景层,如果动画元素或者受布局影响,或者动的过程中影响到了布局,就会打破前景背景的界限,这样简单分为2层就有问题。那么,应用position: fixed | absolute是不是就能保证不会影响布局了?

不行,因为left可以接受百分比值、相对单位(em、vw等等),浏览器不能百分百肯定该属性的变化与布局无关,所以不能简单的分出前景背景层,例如:

代码语言:javascript
复制
@keyframes move {
  from { left: 30px; }
  to { left: 100%; }
}

但浏览器能百分百肯定transformopacity的变化与布局无关,不受布局影响,其变化也不会影响现有布局,所以这两个属性的特殊性是:

  • does not affect the document’s flow,
  • does not depend on the document’s flow,
  • does not cause a repaint.

如果不影响布局,且不受布局影响,其变化不会导致其它部分需要repaint,那么这个东西肯定可以抽出去单独作为一层,放心交给GPU去处理,享受硬件加速带来的好处;

  • 细腻(GPU能做到亚像素级精度,且对GPU来说不费劲)
  • 流畅(不受其它运算密集的JS任务影响,动画交给GPU了,与CPU无关)

三.GPU合成的代价

It might surprise you, but the GPU is a separate computer. That’s right: An essential part of every modern device is actually a standalone unit with its own processors and its own memory- and data-processing models. And the browser, like any other app or game, has to talk with the GPU as it would with an external device.

GPU是独立的一部分,有自己的处理器、内存核数据处理模型,那么意味着通过CPU在内存里创建的图像数据无法直接与GPU共享,需要打包发送给GPU,GPU收到后才能执行我们期望的一系列操作,这个过程需要时间,而打包数据需要内存

需要的内存取决于:

  • 复合层的数量
  • 复合层的大小

相对于数量,复合层的大小影响更大一些,例如:

代码语言:javascript
复制
.rect {
   width: 320px;
   height: 240px;
   background: #f00;
}

这个红块如果要发送给GPU的话,需要的存储空间是:320 × 240 × 3 = 230400B = 225KB(rgb需要3个字节),如果图像含有透明部分,就需要320 × 240 × 4 = 307200B = 300KB

这样一个不起眼的小红块就需要2、300KB,页面动辄几十上百个元素,占全屏半屏的元素也不少,如果都作为复合层,交给GPU,内存消耗可想而知,所以一些很极端的硬件加速场景性能非常差:

gpu compositing issue

对于1GB RAM的设备,去掉系统和后台进程的1/3,再去掉浏览器和当前页面的1/3,实际能用的只有200到300MB,如果复合层太多太大,内存会被迅速消耗,然后掉帧(卡顿、闪烁)现象,甚至浏览器/应用崩溃也就很合理了

P.S.详细见CSS3硬件加速也有坑!!!

四.创建复合层

浏览器在一些情况下会创建复合层,例如:

  • 3D transforms: translate3d, translateZ and so on;
  • <video>, <canvas> and <iframe> elements;
  • animation of transform and opacity via Element.animate();
  • animation of transform and opacity via СSS transitions and animations;
  • position: fixed;
  • will-change;
  • filter;
  • 。。。

还有很多,详细见CompositingReasons.h中定义的常量,分为几类:

这些大多是我们期望的,算是显式创建的复合层,而另一些情况也会创建复合层:

  • 位于复合层之上的元素会被创建复合层(B的z-index大于A,对A做动画,B也会被塞进独立的复合层)

很容易理解,A在动画过程中可能会与B产生重叠,被B遮住,那么GPU需要每帧对A图层做动画,然后再与B图层合成,才能得到正确结果,所以B无论如何都要被塞进复合层,连同A一起交给GPU

隐式创建复合层主要出于重叠考虑,如果浏览器不确定会不会发生重叠,那么就要把不确定的东西都塞进复合层,所以,从这个角度看,高z-index原则是有道理的

五.硬件加速的优缺点

优点

  • 动画非常流畅,能达到60fps
  • 动画执行过程在独立线程里,不受计算密集的JS任务影响

缺点

  • 把元素塞进复合层时需要额外重绘,有时很慢(可能需要整页重绘)
  • 复合层数据传递给GPU有额外时耗,取决于复合层的数量和大小,这在中低端设备可能会导致闪烁
  • 每个复合层都要消耗一部分内存,移动设备上内存很贵,过多占用会导致浏览器/应用崩溃
  • 存在隐式复合层的问题,不注意的话内存飙升
  • 文字模糊,元素有时会变形

最主要的问题集中在内存消耗repaint上,所以动画性能优化目标是降低内存消耗,减少repaint

六.性能优化技巧

1.尽量避免隐式复合层

复合层直接影响repaint、内存消耗:动画开始时创建复合层、结束时删除复合层,都会引起repaint,而动画开始时必须把图层数据发送给GPU,内存消耗集中在这里。两条建议:

  • 给动画元素应用高z-index,最好直接作为body的子元素,对于嵌套很深的动画元素,可以复制一个到body下,仅用于实现动画效果
  • 给动画元素应用will-change,浏览器会提前把这些元素塞进复合层,可以让动画开始/结束时更流畅些,但不能滥用,在不需要的时候赶紧去掉,减少内存消耗

2.只改变transform和opacity

能用transformopacity优先用,不能用的话想办法用,比如背景色渐变,可以用盖在上面的伪元素背景色opacity动画模拟;box-shadow动画可以用铺在下面的伪元素opacity动画模拟,这些曲折的实现方式能带来显著性能提升

3.减少复合层的大小

小元素放大展示,减小widthheight,减少传递给GPU的数据,由GPU做scale放大展示,视觉效果无差异(多用于纯色背景元素,对不太重要的图片也可以进行5%10%的宽高压缩),例如:

代码语言:javascript
复制
<div id="a"></div>
<div id="b"></div><style>
#a, #b {
   will-change: transform;
   background-color: #f00;
}#a {
   width: 100px;
   height: 100px;
}#b {
   width: 10px;
   height: 10px;
   transform: scale(10);
}
</style>

最终显示的两个红色块在视觉上没有差异,但减小了90%的内存消耗

4.考虑对子元素动画与容器动画

容器动画可能存在不必要的内存消耗,比如子元素之间的空隙,也会被当做有效数据发送给GPU,如果对各个子元素分别应用动画,就能避免这部分的内存消耗

例如12道太阳光线旋转,转容器就把容器整张图都发送给GPU,单独转12道光线就去掉了光线之间的11条空隙,能够节省一半内存

5.早早关注复合层的数量和大小

从一开始就关注复合层,尤其是隐式创建的复合层,避免后期优化影响布局

复合层的大小比数量影响更大,但浏览器会做一些优化操作,把几个复合层整合成一个,叫Layer Squashing,但有时一个大复合层比几个小复合层消耗的内存更多,有必要的话可以手动去掉这种优化:

代码语言:javascript
复制
// 给每个元素应用不同的translateZ
translateZ(0.0001px), translateZ(0.0002px)

6.不要滥用硬件加速

没事不要乱加transform: translateZ(0)will-change: transform等强制开启硬件加速的属性,GPU合成存在缺点和不足,而且是非标准的行为,最好情况能带来显著性能提升,最坏情况可能会让浏览器崩溃

参考资料

  • GPU Animation: Doing It Right:一篇文章看了一天
  • 理解WebKit和Chromium: Chromium硬件加速合成
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端问问 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.硬件加速是非规范的
    • 硬件加速(Hardware Acceleration)
    • 二.transform和opacity的特殊性
    • 三.GPU合成的代价
    • 四.创建复合层
    • 五.硬件加速的优缺点
    • 优点
    • 缺点
    • 六.性能优化技巧
      • 1.尽量避免隐式复合层
        • 2.只改变transform和opacity
          • 3.减少复合层的大小
            • 4.考虑对子元素动画与容器动画
              • 5.早早关注复合层的数量和大小
                • 6.不要滥用硬件加速
                  • 参考资料
                  相关产品与服务
                  GPU 云服务器
                  GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档