WHAT - CSS Animationtion 动画系列(三)- 动画卡顿分析

一、背景

自 HTML 和 CSS 诞生以来,开发者在项目中经常使用动画来优化视觉效果,提升用户体验和留存,从远古时期 IE6 的各种滤镜到现在的 CSS3、canvas、SVG,愈发复杂的动画效果在即便是硬件性能快速增长的今天,性能问题一直是困扰我们的难题之一,本文旨在探讨影响动画性能的因素并寻找解决的办法。

动画卡顿可能是由多种原因引起的,下面罗列一些常见的分析和解决方法:

  1. 性能问题:动画卡顿通常是因为动画的渲染速度跟不上帧率的要求,导致画面不流畅。这可能是因为代码执行效率低下、大量 DOM 元素操作、复杂的 CSS 样式计算等原因导致的。解决方法包括优化 JavaScript 代码减少 DOM 操作简化 CSS 样式等。

  2. 内存泄漏:内存泄漏可能导致页面卡顿和性能下降。如果页面中有大量的动态生成的元素或者对象没有被正确地释放,会导致内存占用过高,从而影响页面性能。解决方法包括及时释放不再需要的对象和资源避免创建过多的临时对象等。

  3. 浏览器兼容性问题:不同浏览器对动画的处理方式和性能有所差异,某些浏览器可能不支持某些 CSS 或 JavaScript 特性,导致动画在特定浏览器中出现卡顿或者不流畅的情况。解决方法包括使用浏览器兼容性较好的特性、进行兼容性测试和调整等。

  4. 硬件加速:使用硬件加速可以提高动画的性能和流畅度,尤其是在移动设备上。可以通过 CSS 属性 transformopacityfilter 等来触发硬件加速,从而提高动画的渲染速度和流畅度。

  5. 帧率控制:帧率过高可能会导致动画卡顿,特别是在性能较低的设备上。因为虽然高帧率可以提供更流畅的动画效果,但过高时可能会超出系统计算资源的处理能力,导致页面卡顿。可以通过调整动画的帧率来降低 CPU 和 GPU 的负载,从而提高动画的性能和流畅度。

综上所述,要解决动画卡顿问题,首先需要分析问题的根本原因,然后采取相应的优化和调整措施来提高动画的性能和流畅度。

二、动画卡顿具体分析

参考文档:https://jelly.jd.com/exp/detail?id=5dc38940b73b47015299a49c

大多数设备的刷新频率是 60 帧/秒,也就是 1 秒钟的动画是由 60 个画面连在一起生成的,所以一般要求浏览器对每一帧画面的渲染工作要在 16ms 内完成。当渲染时间超出 16ms 时,1 秒钟内少于 60 个画面生成,就会有不连贯、卡顿的感觉,影响用户体验。

一个页面帧在客户端的渲染分为以下几步:

请添加图片描述

  1. JavaScript。实现动画效果、DOM 操作等
  2. Style。确认每个 DOM 元素应用的 CSS 样式规则
  3. Layout。计算每个 DOM 元素最终在屏幕上的大小和位置
  4. Paint。在多个图层上绘制 DOM 元素的文字、颜色、边框和阴影等效果
  5. Composite。按照合理的顺序合并图层并显示在屏幕上

浏览器在实际渲染页面的时候需要经过一系列的映射,由 HTML 页面构建出来的 DOM 树到最终的图层,映射过程如下图所示:

请添加图片描述

  1. Node -> RenderObject。DOM 树的每一个 Node 都有一个对应的 RenderObject(一对一关系)
  2. RenderObject -> RenderLayer。一个或多个 RenderObject 对应一个 RenderLayer(多对一),RenderLayer 用于保证元素之间的层级关系,一般来说位于同一位置且层级相同的元素处于同一个 RenderLayer。只有某些特殊的 RenderObject 会专门创建一个新的渲染层,其他的 RenderObject 与第一个拥有的 RenderLayer 的祖先元素共用一个,比如上图中的 p 元素和其子元素 div。

常见的生成 RenderLayer 的 RenderObject 拥有如下一种特征:

  1. 页面根元素
  2. 有CSS定位属性(relative, absolute, fixed, sticky)
  3. transparent不为1
  4. overflow不为visible
  5. 有CSS mask属性
  6. 有CSS box-reflect属性
  7. 有CSS filter属性
  8. 3D或硬件加速的2D canvas元素
  9. video元素

可以看出,具有上述特征的 RenderObject 会专门生产新的 RenderLayer,图层能够阻⽌该节点的渲染⾏为影响别的节点,即对页面的某些部分进行独立的处理,从而提升渲染性能,⽐如对于 video 标签来说,浏览器会⾃动将该节点变为图层。但 RenderLayer 过多就会影响页面的渲染速度,也会消耗内存资源,因此应该谨慎使用。

可以使用开发者工具来检查页面的图层信息,以便了解哪些部分被设置为图层,以及优化渲染性能。在 Chrome 浏览器中,在右上角更多工具找到图层

  1. RenderLayer -> GraphicsLayer。一个或多个 RenderLayer 对应一个 GraphicsLayer(多对一)。某些被认为是 Compositing Layer 的 RenderLayer 单独对应一个 GraphicsLayer,其他 RenderLayer将与第一个拥有 GraphicsLayer 的祖先元素共用同一个 GraphicsLayer。对于 GraphicsLayer 来说,每一个 GraphicsLayer 有一个 GraphicsContext 用于绘制其对应的 RenderLayers,合成器 Composite 将 GraphicsContexts 的位图(这个将由GPU实现)合成最终显示在屏幕上。

常见的 RenderLayer 会被提升为 Compositing Layer(单独用一个 GraphicsLayer) 的原因:

  1. 有3D transform属性。使用 translate3d(), translateZ(), scale3d(), rotate3d() 等方法来对元素进行 3D 变换
  2. 有perspective属性。设置元素的透视效果
  3. 3D canvas或硬件加速的2D canvas
  4. 硬件加速的iframe元素(如iframe嵌入的页面有合成层,合成层需要硬件加速)
  5. 使用了硬件加速的插件,如flash
  6. 对opacity/transform属性应用了animation/transition(当animation/transition为active)
  7. 子元素是compositing layer
  8. 兄弟元素是compositing layer,与当前的非composting layer有重叠,层级低于当前层
  9. 有will-change属性。明确告知浏览器该元素将会发生变化,可以预先进行优化处理

注意,提升为 Compositing Layer 可以提高页面的渲染速度。

三、具体优化方法

通过上面分析和学习,我们对优化方向已经有了一定着手点。

3.1 JavaScript:优化 JavaScript 代码

1. requestAnimationFrame 优化

关于 WHAT - requestAnimationFrame 介绍 中我们介绍过“对于动画效果,推荐使用 requestAnimationFrame 方法,它可以更有效地与浏览器的绘制周期同步,提供更流畅的动画效果,并且不会出现页面不可见时的执行问题”。

并且在 HOW - 前端定时器实践(含防抖、interval 模拟) 中我们也详细介绍过计时器存在的问题,计时器无法保证回调函数的执行时机,可能会在一帧内一并执行多次导致多次页面渲染,浪费 CPU 资源甚至产生卡顿,或者是在一帧即将结束时执行导致重新渲染,出现掉帧问题。

而使用 requestAnimationFrame:

  1. 更好的函数节流,其循环间隔是由屏幕刷新频率决定的,保证回调函数在屏幕的每一次刷新间隔中只执行一次
  2. 当页面被隐藏或最小化时,暂停渲染

优化效果比较:推荐在 https://codesandbox.io 调试

  <button id="choice-1">setTimeout</button>
  <button id="choice-2">requestAnimationFrame</button>
  <button id="clear">clearAll</button>
  <div id="result"></div>
  // setTimeout 3次渲染
  document.getElementById('choice-1').addEventListener('click', function () {
   
    document.getElementById('result').innerHTML = '';
    var i = 0;
    while (i < 5000) {
   
      var spanNode = document.createElement('span');
      var txt = document.createTextNode('time');
      spanNode.appendChild(txt);
      document.getElementById('result').appendChild(spanNode);
      i += 1;
    }
    setTimeout(function () {
   
      var j = 0;
      while (j < 200) {
   
        console.log(j);
        j += 1;
      }
      while (j > 0) {
   
        var divNode = document.createElement('div');
        var txt = document.createTextNode('1111111');
        divNode.appendChild(txt);
        document.getElementById('result').appendChild(divNode);
        j -= 1;
      }
      setTimeout(function 
  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值