俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

盒子端 CSS 动画性能提升研究俄罗斯贵宾会:

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

2017/12/08 · CSS · 动画

本文作者: 伯乐在线 - chokcoco 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者

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

基于此,对于 Web 动画的性能问题,仅仅停留在感觉已经优化的OK之上,是不够的,想要在盒子端跑出高性能接近 60 FPS 的流畅动画,就必须要刨根问底,深挖每一处可以提升的方法。

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

流畅动画的标准

理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,所以通常来讲 FPS 为 60frame/s 时动画效果最好,也就是每帧的消耗时间为 16.67ms。

基于此,对于 Web 动画的性能问题,仅仅停留在感觉已经优化的OK之上,是不够的,想要在盒子端跑出高性能接近 60 FPS 的流畅动画,就必须要刨根问底,深挖每一处可以提升的方法。

直观感受,不同帧率的体验

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
  • 帧率波动很大的动画,亦会使人感觉到卡顿。

 

盒子端动画优化

在腾讯视频客厅盒子端,Web 动画未进行优化之前,一些复杂动画的帧率仅有 10 ~ 30 FPS,卡顿感非常明显,带来很不好的用户体验。

而进行优化之后,能将 10 ~ 30 FPS的动画优化至 30 ~ 60 FPS,虽然不算优化到最完美,但是当前盒子硬件的条件下,已经算是非常大的进步。

流畅动画的标准

理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,所以通常来讲 FPS 为 60frame/s 时动画效果最好,也就是每帧的消耗时间为 16.67ms。

盒子端 Web 动画性能比较

首先先给出在盒子端不同类型的Web 动画的性能比较。经过对比,在盒子端 CSS 动画的性能要优于 Javascript 动画,而在 CSS 动画里,使用 GPU 硬件加速的动画性能要优于不使用硬件加速的性能。

所以在盒子端,实现一个 Web 动画,优先级是:

GPU 硬件加速 CSS 动画 > 非硬件加速 CSS 动画 > Javascript 动画

 

直观感受,不同帧率的体验

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
  • 帧率波动很大的动画,亦会使人感觉到卡顿。

 

动画性能上报分析

要有优化,就必须得有数据做为支撑。对比优化前后是否有提升。而对于动画而言,衡量一个动画的标准也就是 FPS 值。

所以现在的关键是如何计算出每个动画运行时的帧率,这里我使用的是 requestAnimationFrame这个函数近似的得到动画运行时的帧率。

考虑到盒子都是安卓系统,且大多版本较低且硬件性能堪忧,导致一是许多高级 API 无法使用,二是这里只是近似得到动画帧率

原理是,正常而言 requestAnimationFrame 这个方法在一秒内会执行 60 次,也就是不掉帧的情况下。假设动画在时间 A 开始执行,在时间 B 结束,耗时 x ms。而中间 requestAnimationFrame 一共执行了 n 次,则此段动画的帧率大致为:n / (B – A)。

核心代码如下,能近似计算每秒页面帧率,以及我们额外记录一个 allFrameCount,用于记录 rAF 的执行次数,用于计算每次动画的帧率 :

var rAF = function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); } ); }(); var frame = 0; var allFrameCount = 0; var lastTime = Date.now(); var lastFameTime = Date.now(); var loop = function () { var now = Date.now(); var fs = (now

  • lastFameTime); var fps = Math.round(1000 / fs); lastFameTime = now; // 不置 0,在动画的开头及结尾记录此值的差值算出 FPS allFrameCount++; frame++; if (now > 1000 + lastTime) { var fps = Math.round((frame * 1000) / (now - lastTime)); // console.log('fps', fps); 每秒 FPS frame = 0; lastTime = now; }; rAF(loop); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var rAF = function () {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        }
    );
}();
 
var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();
 
var loop = function () {
    var now = Date.now();
    var fs = (now - lastFameTime);
    var fps = Math.round(1000 / fs);
 
    lastFameTime = now;
    // 不置 0,在动画的开头及结尾记录此值的差值算出 FPS
    allFrameCount++;
    frame++;
 
    if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime));
        // console.log('fps', fps); 每秒 FPS
        frame = 0;
        lastTime = now;
    };
 
    rAF(loop);
}

 

盒子端动画优化

在腾讯视频客厅盒子端,Web 动画未进行优化之前,一些复杂动画的帧率仅有 10 ~ 30 FPS,卡顿感非常明显,带来很不好的用户体验。

而进行优化之后,能将 10 ~ 30 FPS的动画优化至 30 ~ 60 FPS,虽然不算优化到最完美,但是当前盒子硬件的条件下,已经算是非常大的进步。

研究结论

所以,我们的目标就是在使用 GPU 硬件加速的基础之上,更深入的去优化 CSS 动画,先给出最后的一个优化步骤方案:

  1. 精简 DOM ,合理布局
  2. 使用 transform 代替 left、top,减少使用耗性能样式
  3. 控制频繁动画的层级关系
  4. 考虑使用 will-change
  5. 使用 dev-tool 时间线 timeline 观察,找出导致高耗时、掉帧的关键操作

下文会有每一步骤的具体分析解释。

 

盒子端 Web 动画性能比较

首先先给出在盒子端不同类型的Web 动画的性能比较。经过对比,在盒子端 CSS 动画的性能要优于 Javascript 动画,而在 CSS 动画里,使用 GPU 硬件加速的动画性能要优于不使用硬件加速的性能。

所以在盒子端,实现一个 Web 动画,优先级是:

GPU 硬件加速 CSS 动画 > 非硬件加速 CSS 动画 > Javascript 动画

 

Web 每一帧的渲染

要想达到 60 FPS,每帧的预算时间仅比 16 毫秒多一点 (1 秒/ 60 = 16.67 毫秒)。但实际上,浏览器有整理工作要做,因此您的所有工作需要尽量在 10 毫秒内完成。

而每一帧,如果有必要,我们能控制的部分,也是像素至屏幕管道中的关键步骤如下:俄罗斯贵宾会 1

完整的像素管道 JS / CSS > 样式 > 布局 > 绘制 > 合成:

  1. JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。
  2. 样式计算。此过程是根据匹配选择器(例如 .headline 或 .nav > .nav__item)计算出哪些元素应用哪些 CSS 3. 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
  3. 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
  4. 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  5. 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

当然,不一定每帧都总是会经过管道每个部分的处理。我们的目标就是,每一帧的动画,对于上述的管道流程,能避免则避免,不能避免则最大限度优化。

 

动画性能上报分析

要有优化,就必须得有数据做为支撑。对比优化前后是否有提升。而对于动画而言,衡量一个动画的标准也就是 FPS 值。

所以现在的关键是如何计算出每个动画运行时的帧率,这里我使用的是 requestAnimationFrame这个函数近似的得到动画运行时的帧率。

style="font-family: verdana, geneva; font-size: 14px;">考虑到盒子都是安卓系统,且大多版本较低且硬件性能堪忧,导致一是许多高级 API 无法使用,二是这里只是近似得到动画帧率

原理是,正常而言 requestAnimationFrame 这个方法在一秒内会执行 60 次,也就是不掉帧的情况下。假设动画在时间 A 开始执行,在时间 B 结束,耗时 x ms。而中间 requestAnimationFrame 一共执行了 n 次,则此段动画的帧率大致为:n / (B - A)。

核心代码如下,能近似计算每秒页面帧率,以及我们额外记录一个 allFrameCount,用于记录 rAF 的执行次数,用于计算每次动画的帧率 :

var rAF = function () {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        }
    );
}();

var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();

var loop = function () {
    var now = Date.now();
    var fs = (now - lastFameTime);
    var fps = Math.round(1000 / fs);

    lastFameTime = now;
    // 不置 0,在动画的开头及结尾记录此值的差值算出 FPS
    allFrameCount++;
    frame++;

    if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime));
        // console.log('fps', fps); 每秒 FPS
        frame = 0;
        lastTime = now;
    };

    rAF(loop);
}

  

优化动画步骤

先给出一个步骤,调优一个动画,有一定的指导原则可以遵循,一步一步深入动画:

研究结论

所以,我们的目标就是在使用 GPU 硬件加速的基础之上,更深入的去优化 CSS 动画,先给出最后的一个优化步骤方案:

  1. 精简 DOM ,合理布局
  2. 使用 transform 代替 left、top,减少使用耗性能样式
  3. 控制频繁动画的层级关系
  4. 考虑使用 will-change
  5. 使用 dev-tool 时间线 timeline 观察,找出导致高耗时、掉帧的关键操作

下文会有每一步骤的具体分析解释。

 

1.精简 DOM ,合理布局

这个没什么好说的,如果可以,精简 DOM 结构在任何时候都是对页面有帮助的。

Web 每一帧的渲染

要想达到 60 FPS,每帧的预算时间仅比 16 毫秒多一点 (1 秒/ 60 = 16.67 毫秒)。但实际上,浏览器有整理工作要做,因此您的所有工作需要尽量在 10 毫秒内完成。

而每一帧,如果有必要,我们能控制的部分,也是像素至屏幕管道中的关键步骤如下:

俄罗斯贵宾会 2

完整的像素管道 JS / CSS > 样式 > 布局 > 绘制 > 合成:

  1. JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。

  2. 样式计算。此过程是根据匹配选择器(例如 .headline 或 .nav > .nav__item)计算出哪些元素应用哪些 CSS 3. 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。

  3. 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 <body> 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。

  4. 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。

  5. 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

当然,不一定每帧都总是会经过管道每个部分的处理。我们的目标就是,每一帧的动画,对于上述的管道流程,能避免则避免,不能避免则最大限度优化。

 

2.使用 transform 代替 left、top,减少使用耗性能样式

现代浏览器在完成以下四种属性的动画时,消耗成本较低:

  • position(位置): transform: translate(npx, npx)
  • scale(比例缩放):transform: scale(n)
  • rotation(旋转) :transform: rotate(ndeg)
  • opacity(透明度):opacity: 0...1

如果可以,尽量只使用上述四种属性去控制动画。

不同样式在消耗性能方面是不同的,改变一些属性的开销比改变其他属性要多,因此更可能使动画卡顿。

例如,与改变元素的文本颜色相比,改变元素的 box-shadow 将需要开销大很多的绘图操作。 改变元素的 width 可能比改变其 transform 要多一些开销。如 box-shadow 属性,从渲染角度来讲十分耗性能,原因就是与其他样式相比,它们的绘制代码执行时间过长。

这就是说,如果一个耗性能严重的样式经常需要重绘,那么你就会遇到性能问题。其次你要知道,没有不变的事情,在今天性能很差的样式,可能明天就被优化,并且浏览器之间也存在差异。

优化动画步骤

先给出一个步骤,调优一个动画,有一定的指导原则可以遵循,一步一步深入动画:

本文由俄罗斯贵宾会发布于Web前端,转载请注明出处:盒子端 CSS 动画性能提升研究俄罗斯贵宾会:

您可能还会对下面的文章感兴趣: