写在前面

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

  • 只允许改变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的特殊性

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

@keyframes move {
  from { left: 30px; }
  to { left: 100px; }
}

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

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

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

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

@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收到后才能执行我们期望的一系列操作,这个过程需要时间,而打包数据需要内存

需要的内存取决于:

  • 复合层的数量

  • 复合层的大小

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

.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%的宽高压缩),例如:

<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,但有时一个大复合层比几个小复合层消耗的内存更多,有必要的话可以手动去掉这种优化:

// 给每个元素应用不同的translateZ
translateZ(0.0001px), translateZ(0.0002px)

6.不要滥用硬件加速

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

参考资料

  • GPU Animation: Doing It Right:一篇文章看了一天

  • 理解WebKit和Chromium: Chromium硬件加速合成

转自个人订阅号ayqy

CSS动画与GPU的更多相关文章

  1. CSS动画的性能分析和浏览器GPU加速

    此文已由作者袁申授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 有数的数据大屏可以在一块屏幕上展示若干张不同的图表,以炫酷的方式展示各种业务数据.其中有些图表使用CSS实现了 ...

  2. 这样使用 GPU 渲染 CSS 动画(转)

    大多数人知道现代网络浏览器使用GPU来渲染部分网页,特别是具有动画的部分. 例如,使用transform属性的CSS动画看起来比使用left和top属性的动画更平滑. 但是如果你问,“我如何从GPU获 ...

  3. 梅须逊雪三分白,雪却输梅一段香——CSS动画与JavaScript动画

    CSS动画并不是绝对比JavaScript动画性能更优越,开源动画库Velocity.js等就展现了强劲的性能. 一.两者的主要区别 先开门见山的说说两者之间的区别. 1)CSS动画: 基于CSS的动 ...

  4. css动画属性性能

    性能主要表现:流量.功耗与流畅度 在现有的前端动画体系中,通常有两种模式:JS动画与CSS3动画. JS动画是通过JS动态改写样式实现动画能力的一种方案,在PC端兼容低端浏览器中不失为一种推荐方案. ...

  5. css动画与js动画的区别

    CSS动画 优点: (1)浏览器可以对动画进行优化.   1. 浏览器使用与 requestAnimationFrame 类似的机制,requestAnimationFrame比起setTimeout ...

  6. CSS动画属性性能详细介绍

    CSS动画属性会触发整个页面的重排relayout.重绘repaint.重组recomposite Paint通常是其中最花费性能的,尽可能避免使用触发paint的CSS动画属性,这也是为什么我们推荐 ...

  7. 你所不知道的 CSS 动画技巧与细节

    怕标题起的有点大,下述技巧如果你已经掌握了看看就好,欢迎斧正,本文希望通过介绍一些 CSS 不太常用的技巧,辅以一些实践,让读者可以更加深入的理解掌握 CSS 动画. 废话少说,直接进入正题,本文提到 ...

  8. 盒子端 CSS 动画性能提升研究

    不同于传统的 PC Web 或者是移动 WEB,在腾讯视频客厅盒子端,接大屏显示器(电视)下,许多能流畅运行于 PC 端.移动端的 Web 动画,受限于硬件水平,在盒子端的表现的往往不尽如人意. 基于 ...

  9. 前端性能优化(css动画篇)

    正巧看到在送书,于是乎找了找自己博客上记录过的一些东西来及其无耻的蹭书了~~~ 小广告:更多内容可以看我的博客 最近拜读了一下html5rocks上几位大神写的一篇关于CSS3动画性能优化的文章,学到 ...

随机推荐

  1. [Linux] 无法访问国外网站,完成epel源安装的解决办法--待续

    一.缘由: 由于一个机房的网络限制,无法访问国外IP地址,在安装一些开源软件的时候比如smokeping.ansible就无法从epel源在线安装, 编译安装的话,又需要安装各种依赖,麻烦的一逼.所以 ...

  2. deep learning 练习 牛顿法完成逻辑回归

    Logistic Regression and Newton's Method 作业链接:http://openclassroom.stanford.edu/MainFolder/DocumentPa ...

  3. [DFNews] Cellebrite UFED Logical/Physical Analyzer 3.8.1 维护性更新

    Maintenance Release             Cellebrite has released a maintenance version of UFED Physical / Log ...

  4. [CF442A] Borya and Hanabi (暴力bitmask)

    题目链接:http://codeforces.com/problemset/problem/442/A 题目大意:给你n张卡片,你知道这n张卡片都是什么,但是不知道他们的位置.你每次可以请求朋友指出一 ...

  5. asp.net中插件开发模式说明

    第一定义接口 /// <summary>          /// 这是插件必须实现的接口,也是主程序与插件通信的唯一接口         /// 换句话说,主程序只认识插件里的这些方法  ...

  6. 开源项目大全 >> ...

    http://www.isenhao.com/xueke/jisuanji/kaiyuan.php   监控系统-Nagios 网络流量监测图形分析工具-Cacti 分布式系统监视-zabbix 系统 ...

  7. http://paulgraham.com/arcfaq.html

    Why not use some other delimiter than parentheses?为什么不使用一些其他的分隔符比括号?We tried various possibilities. ...

  8. 通过WMI接口监控服务器性能

    WMI 是微软操作系统的一个内置的组件,通过使用WMI我们可以获取服务器硬件信息.收集服务器性能数据.操作Windows服务,甚至可以远程关机或是重启服务器. 一.在C#编程中使用WMI 要想在C#程 ...

  9. IRC常用命令

    From: http://www.blogjava.net/ivanwan/archive/2006/02/17/31296.html IRC命令可以实现一些特殊的功能.目前,亿言堂支持14条IRC命 ...

  10. Ubuntu16.04 apache2 wsgi 部署django

    在Ubuntu16.04上部署django其实还算简单直观,最重要的问题就是路径设置正确,并且保证版本统一,这个测试是在 Apache/2.4.18 (Ubuntu)  apt-get install ...