HTML5中国

 找回密码
 立即注册

QQ登录

只需一步,快速开始

HTML5中国 首页 应用推荐 查看内容

CSS Animation性能优化(下)

2016-9-30 07:39| 发布者: Hyukoh| 查看: 779| 评论: 0|原作者: Airen的博客|来自: Airen的博客

摘要: CSS Animation是实现Web Animation方法之一,其主要通过 @keyframes 和 animation-* 或者 transition 来实现一些Web动效。

  渲染性能


  在理解渲染性能之前,我们有必要先了解前面提到的两个概念 重排(也就是回流)和 重绘 。因为这两者与前面介绍的像素渲染流水线中的 Layout 和 Paint 都有关系,而且Layout和Paint对性能的渲染又有莫大的关系。


  Reflow(重排)


  Reflow(重排)指的是计算页面布局(Layout)。某个节点Reflow时会重新计算节点的尺寸和位置,而且还有可能触其后代节点Reflow。在这之后再次触发一次Repaint(重绘)

  当Render Tree中的一部分(或全部)因为元素的尺寸、布局、隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,就是页面第一次加载的时候。

  在Web页面中,很多状况下会导致回流:

  •   调整窗口大小
  •   改变字体
  •   增加或者移除样式表
  •   内容变化
  •   激活CSS伪类
  •   操作CSS属性
  •   JavaScript操作DOM
  •   计算 offsetWidth 和 offsetHeight
  •   设置 style 属性的值
  •   CSS3 Animation或Transition

  Repaint(重绘)


  Repaint(重绘)或者Redraw遍历所有节点,检测节点的可见性、颜色、轮廓等可见的样式属性,然后根据检测的结果更新页面的响应部分。

  当Render Tree中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格、而不会影响布局的。就是重绘。

  将重排和重绘的介绍结合起来,不难发现: 重绘(Repaint)不一定会引起回流(Reflow重排),但回流必将引起重绘(Repaint) 。

  既然如此,那么什么情况之下会触发浏览器的Repaint和Reflow呢?


  页面首次加载


  DOM元素添加、修改(内容)和删除(Reflow + Repaint)

  仅修改DOM元素的颜色(只有Repaint,因为不需要调整布局)

  应用新的样式或修改任何影响元素外观的属性


  Resize浏览器窗口和滚动页面


  读取元素的某些属性( offsetLeft 、 offsetTop 、 offsetHeight 、 offsetWidth 、 getComputedStyle() 等)

  可以说Reflow和Repaint都很容易触发,而它们的触发对性能的影响都非常大,但非常不幸的是,我们无法完全避免,只能尽量不去触发浏览器的Reflow和Repaint。

  从前面的内容可以了解到,Reflow和Repaint对性能影响很大,那么具体哪些点会影响到渲染性能呢?


  影响Layout的属性


  当你改变页面上某个元素的时候,浏览器需要做一次重新布局的操作,这次操作会包括计算受操作影响所有元素的几何数,比如每个元素的位置和尺寸。如果你修改了html 这个元素的 width 属性,那么整个页面都会被重绘。

  由于元素相覆盖,相互影响,稍有不慎的操作就有可能导致一次自上而下的布局计算。所以我们在进行元素操作的时候要一再小心尽量避免修改这些重新布局的属性。

  具体有关于会影响Layout的CSS属性可以在 CSS Triggers 网站中查阅。


  影响Repaint的属性


  有些属性的修改不会触发重排,但会触Repaint(重绘),现代浏览器中主要的绘制工作主要用光栅化软件来完成。所以重新会制的元素是否会很大程度影响你的性能,是由这个元素和绘制层级的关系来决定的,如果这个元素盖住的元素都被重新绘制,那么代价自然就相当地大。

  具体有关于会影响Layout的CSS属性可以在 CSS Triggers 网站中查阅。

  如果你在动画里面使用了上述某些属性,导致重绘,这个元素所属的图层会被重新上传到GPU。在移动设备上这是一个很昂贵耗资源的操作,因为移动设备的CPU明显不如你的电脑,这也意味着绘制的工作会需要更长的时间;而上传线CPU和GPU的带宽并非没有限制,所以重绘的纹理上传就自然需要更长的时间。

  CSS Triggers 网站中可以得知哪些属性会触发重排、哪些属性会触发重绘以及哪些属性会触合成。但并不是CSS中所有的属性都可以用于CSS Animation和Transition中的。在W3C官方规范中明确定了哪些CSS属性可以用于 Animation 和 Transition中。 @Rodney Rehm 还对这些 属性做过一个兼容测试 。如果你想深入的了解这方面的知识,建议您阅读下面两篇文章:

  CSS animatable properties

  Thank God We Have A Specification!

  如此一来,我们知道可用于CSS Animation或者Transition的CSS属性之后,再配合CSS Triggers 网站,可以轻易掌握哪些CSS属性会触发重排、重绘和合成等。 虽然无法避免,但我们可以尽量控制 。


  性能优化


  如果我们知道浏览器是如何渲染一个页面的,并且去优化渲染过程中的关键步骤,不是是就能事半功倍呢?

  有关于这部分的介绍,建议大家阅读《 渲染性能 》。

  在像素渲染流水线中,得知,如果我们能幸运的避免Layout和Paint,那么性能是最好的,言外之意,动画性能也将变得最佳。那么在CSS中可能通过不同的方式来创建新图层。其实这也就是大家常说的,通过CSS的属性来触发GPU加速。浏览器会为此元素单独创建一个“层”。当有单独的层之后,此元素的Repaint操作将只需要更新自己,不用影响到别人。你可以将其理解为局部更新。所以开启了硬件加速的动画会变得流畅很多。

  为什么开启硬件加速动画就会变得流畅,那是因为每个页面元素都有一个独立的Render进程。Render进程中包含了主线程和合成线程,主线程负责:

  •   JavaScript的执行
  •   CSS样式计算
  •   计算Layout
  •   将页面元素绘制成位图(Paint)
  •   发送位图给合成线程

  合成线程则主要负责:

  •   将位图发送给GPU
  •   计算页面的可见部分和即将可见部分(滚动)
  •   通知GPU绘制位图到屏幕上(Draw)
  •   我们可以得到一个大概的浏览器线程模型:

  我们可以将页面绘制的过程分为三个部分:Layout、Paint和合成。Layout负责计算DOM元素的布局关系,Paint负责将DOM元素绘制成位图,合成则负责将位图发送给GPU绘制到屏幕上(如果有 transform 、 opacity 等属性则通知GPU做处理)。

  GPU加速其实是一直存在的,而如同 translate3D 这种hack只是为了让这个元素生成独立的 GraphicsLayer , 占用一部分内存,但同时也会在动画或者Repaint的时候不会影响到其他任何元素,对高刷新频率的东西,就应该分离出单独的一个 GraphicsLayer。

  GPU对于动画图形的渲染处理比CPU要快。

  RenderLayer 树,满足以下任意一点的就会生成独立一个 RenderLayer。

  •   页面的根节点的RenderObject
  •   有明确的CSS定位属性( relative , absolute 或者 transform )
  •   是透明的
  •   有CSS overflow、CSS alpha遮罩(alpha mash)或者CSS reflection
  •   有CSS 滤镜(fliter)
  •   3D环境或者2D加速环境的canvas元素对应的RenderObject
  •   video元素对应的RenderObject

  每个RenderLayer 有多个 GraphicsLayer 存在

  •   有3D或者perspective transform的CSS属性的层
  •   使用加速视频解码的video元素的层
  •   3D或者加速2D环境下的canvas元素的层
  •   插件,比如flash(Layer is used for a composited plugin)
  •   对 opacity 和 transform 应用了CSS动画的层
  •   使用了加速CSS滤镜(filters)的层
  •   有合成层后代的层
  •   同合成层重叠,且在该合成层上面(z-index)渲染的层

  每个GraphicsLayer 生成一个 GraphicsContext, 就是一个位图,传送给GPU,由GPU合成放出。

  那么就是说,GraphicsLayer过少则每次repaint大整体的工作量巨大,而过多则repaint小碎块的次数过多。这种次数过多就称为 层数爆炸 ,为了防止这个爆炸 Blink 引擎做了一个特殊处理。

  有关于这部分内容的详细介绍,可以阅读《 无线性能优化:Composite 》一文。

  扯了这么多,我们可以稍微总结一下下:

  不是所有属性动画消耗的性能都一样,其中消耗最低的是 transform 和 opacity两个属性(当然还有会触发Composite的其他CSS属性),其次是Paint相关属性。所以在制作动画时,建议 使用 transform 的 translate 替代 margin 或 position 中的 top 、 right 、 bottom 和 left ,同时使用 transform 中的 scaleX 或者 scaleY 来替代 width 和 height 。

  为了确保页面的流程,必须保证 60fps 内不发生两次渲染树更新,比如下图, 16ms 内只发生如下几个操作则是正常及正确的:

  页面滚动时,需要避免不必要的渲染及长时间渲染。其中不必要的渲染包括:

  •   position:fixed; 。 fixed 定位在滚动时会不停的进行渲染,特别是页面顶部有一个 fixed ,页面底部有个类似返回顶部的 fixed ,则在滚动时会对整个页面进行渲染,效率非常低。可以通过 transform: translateZ(0) 或者 transform: translate3d(0,0,0) 来解决
  •   overflow:scroll 。前面说了,而在滚动也会触发Repaint和Reflow。在调试过程中注意到一个有趣的现象,有时打开了页面并不会导致crash,但快速滑动的时候却会。由于crash是页面本身内存占比过高,只要优化了页面的内存占用,滑动自然也不会是很大的问题。无论你在什么时候滑动页面,页面滚动都是一个不断重新组合重新绘制的过程。所以 减少渲染区域在滚动里就显得非常重要 。
  •   CSS伪类触发。有些CSS伪类在页面滚动时会不小心触发到。比如 :hover 效果有box-shadow 、 border-radius 等比较耗时的CSS属性时,建议页面滚动时,先取消 :hover 效果,滚动停止后再加上 :hover 效果。这个可以通过在外层添加类名进行控制。但添加类名、删除类名也会改变元素时,浏览器就会要重新做一次计算和布局。所以千万要小心这种无意触发重新布局的操作,有的时候可能不是动画,但去付出的代价要比做一个动画更加昂贵。也就是说 classname 变化了,就一定会出现一次rendering计算,如果一定需要这么做,那可以使用 classlist 的方法。
  •   touch 事件的监听

  长时间渲染包括:

  •   复杂的CSS
  •   Image Decodes:特别是图片的Image Decodes及Image Resize这两个过程在移动端是非常耗时的
  •   Large Empty Layers: 大的空图层

  在CSS中除了开启3D加速能明显的让动画变得流畅之外,在CSS中提供了一个新的CSS特性: will-change 。其主要作用就是 提前告诉浏览器我这里将会进行一些变动,请分配资源(告诉浏览器要分配资源给我) 。

  will-change 属性,允许作者提前告知浏览器的默认样式,那他们可能会做出一个元素。它允许对浏览器默认样式的优化如何提前处理因素,在动画实际开始之前,为准备动画执行潜在昂贵的工作。有关于 will-change 更详细的介绍可以点击这里。

  话说回来, will-change 并不是万能的,不是说使用了 will-change 就对动画的性能有提高,而是要正确使用,才会有所改为。在使用 will-change 时应该注意:

  •   不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。

  有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。

  •   不要过早应用 will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。
  •   给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change 属性。

  在使用 will-change 一定要注意方式方法,比如常见的错误方法是直接在 :hover是使用,并没有告诉浏览器分配资源:

.element:hover {
    will-change: transform;
    transition: transform 2s;
    transform: rotate(30deg) scale(1.5);
}

  其正确使用的方法是,在进入父元素的时候就告诉浏览器,你该分配一定的资源:

.element {
    transition: opacity .3s linear;
}
/* declare changes on the element when the mouse enters / hovers its ancestor */
.ancestor:hover .element {
    will-change: opacity;
}
/* apply change when element is hovered */
.element:hover {
    opacity: .5;
}

  另外在应用变化之后,取消 will-change 的资源分配:

var el = document.getElementById('demo');
el.addEventListener('animationEnd', removeHint);

function removeHint() {
    this.style.willChange = 'auto';
}

  除了 will-change 能让我们在制作动画变得更为流畅之外,在CSS层面上,还有别的方案吗?这个答案是肯定的。前面通过大幅的篇幅了解到,影响性能主要是因为重绘和重排。针对于这方面,CSS提供了一个新的属性 contain 。

  有关于这方面的详细介绍,可以阅读《 Chrome 52 中的 CSS Containment 属性》一文。


  总结


  本文主要介绍了浏览器渲染一个Web页面的原理,从中了解到影响Web的性能因素,从底层找到影响Web渲染性能的主要因素是CSS的属性会触发浏览器的重绘、重排。而CSS的动画,也主要是控制CSS的属性,从这一方面说明,影响动画的性能也是造成重绘和重排的CSS。为了让一个动画能更佳的流畅,我们就要从技术的手段避免CSS的属性造成浏览器的重绘和重排。以及利用一些CSS的新属性,让动画的性能更好,也就是让动画更为流畅。



来源作者:Airen的博客

鲜花

握手

雷人

路过

鸡蛋
更多

相关阅读

最新评论

HTML5中国微信

小黑屋|关于我们|HTML5论坛|友情链接|手机版|HTML5中国 ( 京ICP备11006447号 京公网安备:11010802018489号  

GMT+8, 2017-6-27 10:11

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

返回顶部