前端性能-动画性能
Posted natsu-cc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端性能-动画性能相关的知识,希望对你有一定的参考价值。
前言:
我们都知道浏览器是单线程运行的,虽然说浏览器执行js是单线程执行(注意:不是说浏览器只有一个线程),实际上浏览器的两个个重要的执行线程,这 两 个线程协同工作来渲染网页:主线程和合成线程。
相应地,合成线程负责:通过 GPU 将位图绘制到屏幕上;通知主线程更新页面中可见或即将变成可见的部分的位图;计算出页面中哪部分是可见的;计算出当你在滚动页面时哪部分是即将变成可见的;当你滚动页面时将相应位置的元素移动到可视区域。
而我们经常遇到的动画卡顿就是主线程和合成线程的调度不合理。
渲染过程:
网页动画的每一帧都是一次重新渲染,每秒低于24帧的动画,人眼就能感受到停顿,每秒30-60帧才比较流畅 浏览器会按照大多数显示器的刷新频率60Hz来刷新动画, 如果想达到60FPS,就意味着每一帧的任务耗时不能高于16.67ms。
浏览器渲染每一帧的过程:
大致的渲染阶段:
- 解析html建立dom树
- 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU(GPU进程:最多一个,用于3D绘制等),GPU会将各层合成(composite),显示在屏幕上。
- javascript:使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等;
- Style:样式计算,此过程是根据匹配选择器计算出哪些元素应用哪些 CSS3 规则的过程,将应用规则并计算每个元素的最终样式。
- Layout:知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置,网页的布局模式意味着一个元素可能影响其他元素。
- Paint:绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
- Composite:由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。
“生成布局”(flow)和 “绘制”(paint)这两步,合称为"渲染"(render)。重新渲染就是需要重新生成布局和重新绘制。
重排和重绘会不断触发,这是不可避免的,还会消耗大量的CPU和GPU资源,前端性能优化最主要的优化点就是尽量减少重绘和重排。这个可以了阅读之前写的一篇文章重绘和重排。
影响渲染性能的因素:
影响网页渲染的因素,样式表越简单,重绘和重排越快,重绘和重排的DOM元素层级越高,成本就越高,因此我们在开发前端页面时就需要精简DOM元素,合理布局。
提高渲染性能的操作(重绘和重排):
- DOM元素读写分离;
- 让进行大量动画的元素脱离文档流,减少重排开销;
- 通过改变元素的class或csstext一次性的更改样式;
- 缓存DOM元素的位置信息,避免不必要的属性读取;
- 尽量使用离线DOM;
- 避免使用table布局;
- 使用css3 transform优化动画性能;
回归动画:
上面说到使用css3的transform,下面就解释下为啥推荐使用transform?
transform为我们提供了丰富的api,例如scale,translate,rotate, skew等等。这是通过修改CSS 可视格式模型的坐标空间来实现的。
css3 transform 的执行效率:
参考别人的一个例子:
<!-- height-->
div { height: 100px; transition: height 1s linear; }
div:hover { height: 200px; }
<!-- transform -->
div { transform: scale(0.5); transition: transform 1s linear; }
div:hover { transform: scale(1.0); }
- 通过改变height属性的动画渲染过程:
- 通过transform的scale进行动画的渲染过程:
将每一帧的变化浏览器都在进行布局、绘制、把新的位图交给 GPU 内存,但是在将位图加载到GPU内存中的操作是个相对耗时的操作。
通过两者之间的渲染过程比较可知:transform的动画并没有每次经过主线程的绘制然后再同步到GPU当中,而是直接交给GPU去做这件事,专业的事情交给专业的人去做。我们也可以借助chrome devtools的performance工具对比二者的性能差异。
GPU擅长的事情:
- 绘制位图到屏幕上
- 可不断的绘制相同的位图
- 将同一位图进行位移、旋转、缩放
所以为啥推荐使用transform?
在使用css3 transtion做动画效果时,transform实现的动画是与合成器线程相关的,不需要等待主线程样式计算或者 JS 执行,计算速度是很快的;而使用height,width,margin和padding时,导致布局和绘制的调整,主线程需要重新计算样式并且执行JS,然后再同步到GPU绘制,这当然就慢了。
层的引入:
在页面的渲染过程中,浏览器还具有一系列并不直接暴露给开发者的页面中间表示方式。这些表示方式中最重要的结构就是层。
这些层分别是掌管DOM子树的渲染层(RenderLayer)以及掌管渲染层子树的图形层(GraphicsLayer),某些特殊的渲染层会被认为是合成层(Compositing Layers,合成层拥有单独的 GraphicsLayer。
拥有单独GraphicsLayer的层,都会将位图存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后 绘制到屏幕上。
将渲染层提升为合成层的方式:
- 进行3D或者透视变换的CSS属性;
- 具有3D(WebGL)上下文或者硬件加速的2D上下文的元素;
- 组合型插件(即Flash);
- 具有有CSS透明度动画或CSS变形动画;
- 使用了硬件加速的CSS Filters技术(有的文献中表示filters属性并没有提升为合成层的效果,推测只有一部分filters滤镜效果需要使用硬件加速,并非所有);
- 使用了剪裁Clip或者反射Reflection,并且它的后代中包含一个合成层;
- 子元素中存在具有组合层的元素的元素(换句话说,就是存在具有自己的层的子元素的元素);
- 同级元素中有Z索引比其小的元素,而且该Z索引比较小的元素具有组合层(换句话说就是在组合层之上进行渲染的元素);
提升为合成层的优势:
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快;
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint;
requestAnimationFrame:
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
这个api比setTimeout和setInterval更加适用于动画的制作,跟随浏览器每帧的变化进行每一帧动画的改变,给人感受到动画更加流畅自然,不会受一些同步的代码之类的影响到动画流畅性。
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame();
最终:
附上Chrome Devtool Performance的食用方法,让大家去分析页面运行时的性能表现。
以上是关于前端性能-动画性能的主要内容,如果未能解决你的问题,请参考以下文章
高性能Web动画和渲染原理系列——transform和opacity为什么高性能