CSS3 动画卡顿性能优化的完美解决方案


Posted in Javascript onSeptember 20, 2018

为什么会卡顿?

有一个前提必须要提,前端开发者们都知道,浏览器是单线程运行的。但是我们要明确以下几个概念:单线程,主线程和合成线程。
虽然说浏览器执行js是单线程执行(注意,是执行,并不是说浏览器只有1个线程,而是运行时,runing),但实际上浏览器的2个重要的执行线程,这 2 个线程协同工作来渲染一个网页:主线程和合成线程。
一般情况下,主线程负责:运行 JavaScript;计算 HTML 元素的 CSS 样式;页面的布局;将元素绘制到一个或多个位图中;将这些位图交给合成线程。
相应地,合成线程负责:通过 GPU 将位图绘制到屏幕上;通知主线程更新页面中可见或即将变成可见的部分的位图;计算出页面中哪部分是可见的;计算出当你在滚动页面时哪部分是即将变成可见的;当你滚动页面时将相应位置的元素移动到可视区域。

那么为什么会造成动画卡顿呢?

原因就是主线程和合成线程的调度不合理。
下面来详细说一下调度不合理的原因:

在使用height,width,margin,padding作为transition的值时,会造成浏览器主线程的工作量较重,例如从margin-left:-20px渲染到margin-left:0,主线程需要计算样式margin-left:-19px,margin-left:-18px,一直到margin-left:0,而且每一次主线程计算样式后,合成进程都需要绘制到GPU然后再渲染到屏幕上,前后总共进行20次主线程渲染,20次合成线程渲染,20+20次,总计40次计算。

主线程的渲染流程,可以参考浏览器渲染网页的流程:

使用 HTML 创建文档对象模型(DOM)
使用 CSS 创建 CSS 对象模型(CSSOM)
基于 DOM 和 CSSOM 执行脚本(Scripts)
合并 DOM 和 CSSOM 形成渲染树(Render Tree)
使用渲染树布局(Layout)所有元素
渲染(Paint)所有元素

也就是说,主线程每次都需要执行Scripts,Render Tree ,Layout和Paint这四个阶段的计算。

而如果使用transform的话,例如tranform:translate(-20px,0)到transform:translate(0,0),主线程只需要进行一次tranform:translate(-20px,0)到transform:translate(0,0),然后合成线程去一次将-20px转换到0px,这样的话,总计1+20计算。

可能会有人说,这才提升了19次,有什么好性能提升的?
假设一次10ms。
那么就减少了约190ms的耗时。
会有人说,辣鸡,才190ms,无所谓。
那么如果margin-left是从-200px到0呢,一次10ms,10ms*199≈2s。
还会有人说,辣鸡,也就2s,无所谓。
你忘了单线程这回事了吗?
如果网页有3个动画,3*2s=6s,就是6s的性能提升。
由于数据是猜测的,所以暂时不考虑其真实性

为了增强本文的说服力,下面我就用一个实例来证实下我的观点,大家一起看一下

前端时间用 animation 实现 H5 页面中首页动画过渡,很简单的一个效果,首页加载一个客服头像,先放大,停留 700ms 后再缩小至顶部。代码如下:

<!DOCTYPE html>
<html>
<head lang="zh-cn">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=1" >
  <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
  <title>首页加载动画</title>
  <head>
    <style>
      .welcome-main{
        display: none;        
        padding-bottom: 40px;      
      }
      .top-info{        
        width: 100%;        
        position: absolute;        
        left: 0;        
        top: 93px;      
      }
      .wec-img{
        width: 175px;        
        height: 175px;        
        position: relative;        
        padding: 23px;        
        box-sizing: border-box;        
        margin: 0 auto;      
       }
      .wec-img:before{        
        content: '';        
        position: absolute;        
        left: 0;        
        top: 0;        
        width: 100%;        
        height: 100%;        
        background: url("./images/kf-welcome-loading.png");        
        background-size: 100%;      
       }
      .wec-img .img-con{
        width: 100%;        
        height: 100%;        
        border-radius: 50%;        
        /*box-sizing: border-box;*/
        background: url("./images/kf_1.jpg");        
        background-size: 100%;        
        padding: 1px;      
      }
      .wec-img .img-con img{
        width: 100%;        
        height: 100%;        
        border-radius: 50%;      
      }
      .loaded .wec-img{
        -webkit-transform-origin: center top;      
      }        
      .loading.welcome-main{
        display: block;
      }
      .loading .wec-img{
        -webkit-animation:fadeIn .3s ease both;
      }
      .loading .wec-img:before{
        -webkit-animation:rotate .6s .2s linear both;      
      }
      .loaded .top-info{
        -webkit-animation:mainpadding 1s 0s ease both;      
      }
      .loaded .wec-img{
        -webkit-animation:imgSmall 1s 0s ease both;      }
      @-webkit-keyframes mainpadding{
        0%{-webkit-transform:translateY(0)  
      }
        100%{-webkit-transform:translateY(-87px)   
        }
      }      
      @-webkit-keyframes imgSmall{
        0%{
          width: 175px;          
          height: 175px;          
          padding: 23px;        
        }
        100%{          
          width: 60px;          
          height: 60px;          
          padding: 0;        
        }
      }      
      @-webkit-keyframes fadeIn{
        0%{opacity:0;-webkit-transform:scale(.3)}
        100%{opacity:1;-webkit-transform:scale(1)}
      }      
      @-webkit-keyframes rotate{
        0%{opacity:0;-webkit-transform:rotate(0deg);}
        50%{opacity:1;-webkit-transform:rotate(180deg);}
        100%{opacity:0;-webkit-transform:rotate(360deg);}
      }     
      </style>
    <body>
      <div class="welcome-main">
        <div class="top-info">
          <div class="wec-img"><p class="img-con"><img src="" alt=""></p></div>
        </div>
      </div>
      <script>
        $('.welcome-main').addClass('loading');
        setTimeout(function(){
          $('.hi.fst').removeClass('loading');
          $('.welcome-main').addClass('loaded');
        },700);
      </script>
    </body>
  </html>

在 chrome 上测试 ok,但在提测给 QA 的时候发现部分机型,如华为(系统4.2),oppo(系统5.1)的出现卡顿情况。

百思不得其解,后来参考文章深入浏览器理解 CSS animations 和 transitions 的性能问题一文,将图片缩放中动画元素改成 transform,如下

@-webkit-keyframes imgSmall{
 0%{
   -webkit-transform:scale(1);
 }
 100%{
   -webkit-transform:scale(.465);
 }
}

果然啊,卡顿问题解决了。

文章深入浏览器理解 CSS animations 和 transitions 的性能问题是这么解释的,现代的浏览器通常会有两个重要的执行线程,这 2 个线程协同工作来渲染一个网页:主线程和合成线程。

一般情况下,主线程负责:运行 JavaScript;计算 HTML 元素的 CSS 样式;页面的布局;将元素绘制到一个或多个位图中;将这些位图交给合成线程。

相应地,合成线程负责:通过 GPU 将位图绘制到屏幕上;通知主线程更新页面中可见或即将变成可见的部分的位图;计算出页面中哪部分是可见的;计算出当你在滚动页面时哪部分是即将变成可见的;当你滚动页面时将相应位置的元素移动到可视区域。

假设我们要一个元素的 height 从 100 px 变成 200 px,就像这样:

div {
  height: 100px;
  transition: height 1s linear;
}
 
div:hover {
  height: 200px;
}

主线程和合成线程将按照下面的流程图执行相应的操作。注意在橘黄色方框的操作可能会比较耗时,在蓝色框中的操作是比较快速的。

CSS3 动画卡顿性能优化的完美解决方案

而使用 transform:scale 实现

div {
  transform: scale(0.5);
  transition: transform 1s linear;
}
 
div:hover {
  transform: scale(1.0);
}

此时流程如下:

CSS3 动画卡顿性能优化的完美解决方案

也就是说,使用 transform,浏览器只需要一次生成这个元素的位图,并在动画开始的时候将它提交给 GPU 去处理 。之后,浏览器不需要再做任何布局、 绘制以及提交位图的操作。从而,浏览器可以充分利用 GPU 的特长去快速地将位图绘制在不同的位置、执行旋转或缩放处理。

为了从数量级上去证实这个理论,我打开 chrome 的 Timeline 查看页面 FPS

CSS3 动画卡顿性能优化的完美解决方案

其中,当用 height 做动画元素时,在切换过程的 FPS 只有 44,我们知道每秒 60 帧是最适合人眼的交互,小于 60,人眼能明显感觉到,这就是为什么卡顿的原因。

CSS3 动画卡顿性能优化的完美解决方案

rendering 和 painting 所花的时间如下:

CSS3 动画卡顿性能优化的完美解决方案

再来看看用 transform:scale

CSS3 动画卡顿性能优化的完美解决方案

FPS 达到 66,且 rendering 和 painting 时间减少了 3 倍。

到此为止问题是解决了,隔了几天,看到一篇解决 Chrome 动画”卡顿”的办法,发现还能通过开启硬件加速的方式优化动画,于是又试了一遍。

webkit-transform: translate3d(0,0,0);
-moz-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
-o-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);

惊人的事情发生了,FPS 达到 72:

CSS3 动画卡顿性能优化的完美解决方案CSS3 动画卡顿性能优化的完美解决方案

总结解决 CSS3 动画卡顿方案

1.尽量使用 transform 当成动画熟悉,避免使用 height,width,margin,padding 等;
2.要求较高时,可以开启浏览器开启 GPU 硬件加速。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Javascript 相关文章推荐
浅析Prototype的模板类 Template
Dec 07 Javascript
javascript回车完美实现tab切换功能
Mar 13 Javascript
IE浏览器不支持getElementsByClassName的解决方法
Aug 27 Javascript
JQuery动态添加和删除表格行的方法
Mar 09 Javascript
javascript实现仿腾讯游戏选择
May 14 Javascript
用户代理字符串userAgent可实现的四个识别
Sep 20 Javascript
基于Jquery代码实现手风琴菜单
Nov 19 Javascript
jQuery中serializeArray()与serialize()的区别实例分析
Dec 09 Javascript
JS 面向对象之继承---多种组合继承详解
Jul 10 Javascript
json定义及jquery操作json的方法
Sep 29 Javascript
vue-cli2 构建速度优化的实现方法
Jan 08 Javascript
详解webpack4.x之搭建前端开发环境
Mar 28 Javascript
vue.js中proxyTable 转发请求的实现方法
Sep 20 #Javascript
浅谈React Event实现原理
Sep 20 #Javascript
vue-cli项目代理proxyTable配置exclude的方法
Sep 20 #Javascript
Node批量爬取头条视频并保存方法
Sep 20 #Javascript
vue 本地环境跨域请求proxyTable的方法
Sep 19 #Javascript
vue 优化CDN加速的方法示例
Sep 19 #Javascript
Vue前后端不同端口的实现方法
Sep 19 #Javascript
You might like
生成php程序的php代码
2008/04/07 PHP
php数组函数序列 之shuffle()和array_rand() 随机函数使用介绍
2011/10/29 PHP
简单谈谈php中ob_flush和flush的区别
2014/11/27 PHP
不使用中间变量,交换int型的 a, b两个变量的值。
2010/10/29 Javascript
Grid得到选择行数据的方法总结
2011/01/17 Javascript
jQuery功能函数详解
2015/02/01 Javascript
JS判断网页广告是否被浏览器拦截过滤的代码
2015/04/05 Javascript
Angular页面间切换及传值的4种方法
2016/11/04 Javascript
关于微信上网页图片点击全屏放大效果
2016/12/19 Javascript
详解Vue.js动态绑定class
2016/12/20 Javascript
vue如何实现observer和watcher源码解析
2017/03/09 Javascript
详解webpack中的hash、chunkhash、contenthash区别
2018/01/05 Javascript
浅谈React中的元素、组件、实例和节点
2018/02/27 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
2019/03/28 Javascript
使用Vue-Awesome-Swiper实现旋转叠加轮播效果&amp;平移轮播效果
2019/08/16 Javascript
JavaScript实现简易聊天对话框(加滚动条)
2020/02/10 Javascript
javascript实现下拉菜单效果
2021/02/09 Javascript
Python爬虫框架Scrapy安装使用步骤
2014/04/01 Python
Python中return self的用法详解
2018/07/27 Python
python GUI库图形界面开发之PyQt5 Qt Designer工具(Qt设计师)详细使用方法及Designer ui文件转py文件方法
2020/02/26 Python
python 使用openpyxl读取excel数据
2021/02/18 Python
Stefania Mode美国:奢华设计师和时尚服装
2018/01/07 全球购物
俄罗斯美容和健康网上商店:Созвездие Красоты
2019/07/23 全球购物
优秀大学生的自我评价
2014/01/16 职场文书
先进工作者获奖感言
2014/02/08 职场文书
致裁判员加油稿
2014/02/08 职场文书
文明礼仪演讲稿
2014/05/12 职场文书
优秀电子工程系毕业生求职信
2014/05/24 职场文书
护理专业毕业生自荐信
2014/06/15 职场文书
2014国庆节标语口号
2014/09/19 职场文书
2014领导班子专题民主生活会对照检查材料思想汇报
2014/09/23 职场文书
领导干部作风建设工作总结
2014/10/23 职场文书
CSS布局之浮动(float)和定位(position)属性的区别
2021/09/25 HTML / CSS
Spring Security中用JWT退出登录时遇到的坑
2021/10/16 Java/Android
Android 界面一键变灰 深色主题工具类
2022/04/28 Java/Android
numpy array找出符合条件的数并赋值的示例代码
2022/06/01 Python