02月10, 2017

网页动画性能日志(一)

动画做多了,自然就要考虑性能,我打算出一个系列的日志,详细的讲解一下网页动画性能相关的知识,如果你已经可以运用css3 canvas来做动画,可以来参考一下。

目前我做的最复杂的动画就是360搜索中PC端的天气动画

共包含14个动画场景,每个场景基本由1-3个独立的动画叠加而成,抽象动画共12个,从开发到优化完成共分为四期完成,一二期所有的动画均使用canvas完成,三期四期对动画性能进行大幅度改进,重构了部分代码。其中兼容低倍屏、高倍屏,canvas绘制的折线图浏览器兼容到IE6(具体实现参见:基于canvas折线图统计图),动画兼容到IE9,低版本浏览器动画显示静态图片。所以以下我所有分析均依赖于该项目。

量化流畅的动画

首先我们先来简单的介绍一下动画原理,其实动画本身是不动的,它的实现原理是利用人眼的“视觉暂留”现象,在段时间内连续播放数副精致的画面,使肉眼银视觉残象产生错觉,而产生“动”的概念。

相关概念

  • 帧 (Frame):在动画过程中,每一幅静止画面即为一“帧”。

  • 帧率(Frame per second):即每秒钟播放的静止画面的数量,单位是fps(Frame per second)。

  • 帧时长:每一幅静止画面的停留时间,单位一般是ms(毫秒)。

  • 跳帧(掉帧/丢帧):在帧率固定的动画中,某一帧的时长远高于平均帧时长,导致其后续数帧被挤压而丢失的现象。

    正常情况下浏览器渲染刷新频率稳定在60fps左右,人眼是可以看到流畅平滑的动画的,一般来讲低于30fps的动画,就会有卡顿。

    工欲善其事必先利其器

    来了解一下,怎样使用chrome这把利刃。

    FPS Meter

    alt

    使用这个工具你可以检测当前浏览器GPU渲染动画的帧率。

    • ✔️ 高亮网页中需要重绘的区域
    • ✔️ 将需要重回的区域用橘黄色的边框标注
    • ✔️ 绘制帧率,帧速率分布,以及GPU 缓存
    • ✔️ 展示页面中减慢滚动的区域
    • ✔️ 强制媒体类型来测试绘制和屏幕渲染

    当然以上只能在chrome中进行调试,如果你想在其他的浏览器FPS的检测,你可以使用stats.js,侦听全局或指定位置的帧率,JS实现,所有浏览器可用。

    alt

    Timeline

    alt

    具体使用方式可以参考:chrome官方文档

    profiles

    alt

    taskmanager

    alt

    浏览器内核

    各厂出品的浏览器所用的渲染引擎不尽相同: IE使用Trident ,FireFox使用Gecko ,Safari使用WebKit ,Chrome 28+ 和 Opera 15+使用的是Blink(WebKit的分支) 现代的浏览器通常会有两个重要的执行线程,这2个线程协同工作来渲染一个网页:

  • 主线程

  • 合成线程

    一般情况下,主线程负责:

  • 运行js

  • 计算HTML元素的css样式

  • 页面布局layout

  • 将元素绘制到一个或多个位图中

  • 将这些位图交给合成线程

    合成线程负责:

  • 通过GPU将位图渲染到屏幕

  • 通知主线程更新页面中可见或即将变成可见的部分的位图

  • 计算页面中可见部分

  • 计算出当你在滚动页面时哪部分是即将变成可见的

  • 当你滚动页面时将相应位置的元素移动到可视区域

    长时间执行 JavaScript 或渲染一个很大的元素会阻塞主线程,在这期间,它将无法响应用户的交互。

    相反,合成线程则会尽量去响应用户的交互。当一个页面发生变化时,由于当今大多数设备的屏幕刷新率都是 60次/秒,所以合成线程也会以每秒60 帧的间隔去不断重绘这个页面,即使这个页面不完整。

    当用户滚动页面时,合成线程会通知主线程更新页面中最新可见部分的位图。但是,如果主线程响应地不够快,合成线程不会保持等待,而是马上绘制已经生成的位图,还没准备好的部分用白色进行填充。

    也就是说js是单线程的,但浏览器是多线程的,感兴趣的小伙伴可以看一下我之前翻译的谷歌日志浏览器多进程架构

    浏览器的渲染机制

    大多数设备的屏幕刷新率都是 60次/秒,浏览器对每一帧画面的渲染工作需要在16毫秒(1秒 / 60 = 16.66毫秒)之内完成。但实际上,在渲染某一帧画面的同时,浏览器还有一些额外的工作要做(比如渲染队列的管理,渲染线程与其他线程之间的切换等等)。因此单纯的渲染工作,一般需要控制在10毫秒之内完成,才能达到流畅的视觉效果。如果超过了这个时间限度,页面的渲染就会出现卡顿效果,也就是常说的jank,它是很糟糕的用户体验。

    在web页面中,代码就是经过大概以下步骤转换成屏幕上的显示像素:

    alt

    1.JavaScript:一般来说,我们会使用JavaScript来实现一些视觉变化的效果。

    2.计算样式:这个过程是根据CSS选择器,比如.headline或.nav > .nav_item,对每个DOM元素匹配对应的CSS样式。这一步结束之后,就确定了每个DOM元素上该应用什么CSS样式规则。

    3.布局:上一步确定了每个DOM元素的样式规则,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。

    4.绘制:本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。

    5.渲染层合:由上一步可知,对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

    上述过程的每一步中都有发生jank的可能,因此一定要弄清楚你的代码将会运行在哪一步。

    虽然在理论上,页面的每一帧都是经过上述的流水线处理之后渲染出来的,但并不意味着页面每一帧的渲染都需要经过上述五个步骤的处理。实际上,对视觉变化效果的一个帧的渲染,有这么三种 常用的 流水线:

    1.JS / CSS > 计算样式 > 布局 > 绘制 > 渲染层合并

    alt

    如果你修改一个DOM元素的”layout”属性,也就是改变了元素的样式(比如宽度、高度或者位置等),那么浏览器会检查哪些元素需要重新布局,然后对页面激发一个reflow过程完成重新布局。被reflow的元素,接下来也会激发绘制过程,最后激发渲染层合并过程,生成最后的画面。

    2.JS / CSS > 计算样式 > 绘制 > 渲染层合并

    alt

    如果你修改一个DOM元素的“paint only”属性,比如背景图片、文字颜色或阴影等,这些属性不会影响页面的布局,因此浏览器会在完成样式计算之后,跳过布局过程,只做绘制和渲染层合并过程。

    3.JS / CSS > 计算样式 > 渲染层合并

    alt

    如果你修改一个非样式且非绘制的CSS属性,那么浏览器会在完成样式计算之后,跳过布局和绘制的过程,直接做渲染层合并。第三种的性能最为理想,一般来说对于动画和滚动这种复合很重的渲染,我们就尽量向第三种靠拢。

    性能优化是一门减法艺术,我们要本着经历简化页面徐然过程,然后使每一步的渲染尽可能高效。

    浏览器中CPU vs GPU,如何抉择?

    我们先来说说GPU,大多数手机、 平板电脑、 和计算机都配备了GPU芯片,GPU有着非常专业的定位,这意味着GPU非常擅长做某些事情(比如绘图),但在其他方面则没什么优势。

    现在,我们来看看CPU和GPU的内部特点 CPU(Central Processing Unit),GPU(Graphics Processing Unit)翻译过来,第一个叫做中央处理器,后者叫做视觉处理器,换言之,同样是计算机中用于计算的核心组件,CPU主要负责通用计算,GPU主要负责专用计算。我们来看看下面的这个图理解一下:

    alt

    翻译一下:

  • 强大的ALU - 降低操作延时

  • 巨大的缓存器 - 将长延迟内存访问转换为短延迟缓存访问

  • 复杂的控制器 -分支预测用于减少分支延迟   -数据转发,减少数据延迟

    alt

    翻译一下:

  • 小缓存 -用来提高内存吞吐量

  • 小控制器 -没有分支预测,没有数据转发

  • 高效能ALU - 很多长延时,但是有大量的吞吐流量

    可以看到,CPU中包含Control(控制层),Cache(缓存层),ALU(算术逻辑单元),其中ALU是主要负责进行简单运算的。而GPU中则可以明显的看到包含大量的ALU模块和少量的Cache和Control模块。

    算术逻辑单元(英语:Arithmetic Logic Unit, ALU)[1]是中央处理器的执行单元,是所有中央处理器的核心组成部分,由及闸和或闸构成的算数逻辑单元,主要功能是进行二进制的算术运算,如加减乘(不包括整数除法)。基本上,在所有现代CPU体系结构中,二进制都以二补数的形式来表示。 -----维基百科

    所以可以清晰地从结构中看出,CPU更擅长于逻辑控制,串行运算,GPU更擅长于大规模并发计算。举一个简单的例子,一个教授,带着20个学生完成一个项目,现在项目需要处理1000次100以内的加减乘除运算,这项工作其实并不需要任何逻辑处理,只是大量的工作量的堆叠,这时,就不需要教授亲力亲为,教授可以将任务分配给这20个学生分工完成这项工作,其中教授的角色就相当于CPU,20个学生总体相当于GPU,每个学生就等于一个ALU。

    GPU的处理图像的优势:

  • 绘制位图到屏幕上

  • 一遍又一遍地绘制相同的位图

  • 将同一位图绘制到不同位置,执行旋转以及缩放处理

  • 具有多核简单计算能力,可以处理大量计算数据

    GPU的慢在于:

  • 将位图加载到它的显存中

    JS动画

    缺点:JavaScript在浏览器的主线程中运行,而其中还有很多其他需要运行的JavaScript、样式计算、布局、绘制等对其干扰。这也就导致了线程可能出现阻塞,从而造成丢帧的情况。

    优点:JavaScript的动画与CSS预先定义好的动画不同,可以在其动画过程中对其进行控制:开始、暂停、回放、中止、取消都是可以做到的。而且一些动画效果,比如视差滚动效果,只有JavaScript能够完成。

    CSS动画

    缺点:缺乏强大的控制能力。而且很难以有意义的方式结合到一起,使得动画变得复杂且易于出问题。

    优点:浏览器可以对动画进行优化。它必要时可以创建图层,然后在主线程之外运行,也就是开启GPU加速。

    一般来说,Chrome中满足以下任意情况就会创建图层:

  • 3D或透视变换(perspective transform)CSS属性

  • 使用加速视频解码的<video>节点

  • 拥有3D(WebGL)上下文或加速的2D上下文的 <canvas>节点

  • 混合插件(如Flash)

  • 对自己的opacity做CSS动画或使用一个动画webkit变换的元素

  • 拥有加速CSS过滤器的元素

  • 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)

  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

    需要注意的是,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。所以这需要通过特殊的方式来强制gif图属于自己一个图层(translateZ(0)或者translate3d(0,0,0)),CSS3的动画也是一样。

    展望

    通过web workers 这样的多线程来实现动画


    好啦!基本概念已经铺垫完了,感兴趣的话,就来看看在业务线中如何小试牛刀。网页动画性能日志(二)

    转载请说明出处!

    参考链接:

  • http://blog.csdn.net/leer168/article/details/25917093

  • https://segmentfault.com/a/1190000000490328

  • 本文链接:https://www.imwineki.cn/post/WebPerformanceCalendar.html

    -- EOF --

    Comments

    评论加载中...

    注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。