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 相关文章推荐
javascript dom代码应用 简单的相册[firefox only]
Jun 12 Javascript
js 弹出菜单/窗口效果
Oct 30 Javascript
禁止你的左键复制实用技巧
Jan 04 Javascript
jQuery实现回车键(Enter)切换文本框焦点的代码实例
May 05 Javascript
jQuery通过扩展实现抖动效果的方法
Mar 11 Javascript
jquery简单实现外部链接用新窗口打开的方法
May 30 Javascript
javascript中利用柯里化函数实现bind方法【推荐】
Apr 29 Javascript
实现JavaScript高性能的数据存储
Dec 11 Javascript
JavaScrip数组删除特定元素的几种方法总结
Sep 06 Javascript
使用D3.js构建实时图形的示例代码
Aug 28 Javascript
react高阶组件添加和删除props
Apr 26 Javascript
Node.js安装详细步骤教程(Windows版)详解
Sep 01 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弹出错误警告函数扩展性强
2014/01/17 PHP
php实现的单一入口应用程序实例分析
2015/09/23 PHP
php实现图片上传时添加文字和图片水印技巧
2020/04/18 PHP
Laravel框架实现的上传图片到七牛功能详解
2019/09/06 PHP
用YUI做了个标签浏览效果
2007/02/20 Javascript
JQuery魔力之$(&quot;tagName&quot;)与selector
2012/03/05 Javascript
JS获取屏幕,浏览器窗口大小,网页高度宽度(实现代码)
2013/12/17 Javascript
js实现连个数字相加而不是拼接的方法
2014/02/23 Javascript
javascript结合fileReader 实现上传图片
2015/01/30 Javascript
jquery实现简单手风琴菜单效果实例
2015/06/13 Javascript
浅谈jQuery为哪般去掉了浏览器检测
2016/08/29 Javascript
基于Bootstrap的Metronic框架实现条码和二维码的生成及打印处理操作
2016/08/29 Javascript
nodejs实现的连接MySQL数据库功能示例
2018/01/25 NodeJs
详解Vue打包优化之code spliting
2018/04/09 Javascript
element-ui 限制日期选择的方法(datepicker)
2018/05/16 Javascript
JavaScript引用类型Function实例详解
2018/08/09 Javascript
小程序实现投票进度条
2019/11/20 Javascript
Vue实现简易计算器
2020/02/25 Javascript
vue 弹出遮罩层样式实例
2020/07/22 Javascript
js实现手表表盘时钟与圆周运动
2020/09/18 Javascript
[50:15]VP vs Mineski 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
Python实现Logger打印功能的方法详解
2017/09/01 Python
实例讲解Python中整数的最大值输出
2019/03/17 Python
Python HTML解析模块HTMLParser用法分析【爬虫工具】
2019/04/05 Python
python 随机生成10位数密码的实现代码
2019/06/27 Python
pytorch中的weight-initilzation用法
2020/06/24 Python
html5 Canvas画图教程(8)—canvas里画曲线之bezierCurveTo方法
2013/01/09 HTML / CSS
Html5实现iPhone开机界面示例代码
2013/06/30 HTML / CSS
德国在线购买葡萄酒网站:Geile Weine
2019/09/24 全球购物
酒店行政人事部经理职务说明书
2014/02/26 职场文书
运输服务质量承诺书
2014/03/27 职场文书
家长给学校的建议书
2014/05/15 职场文书
社会实践活动总结范文
2014/07/03 职场文书
2014年教师节演讲稿范文
2014/09/10 职场文书
幼儿园学前班幼儿评语
2014/12/29 职场文书
Oracle 临时表空间SQL语句的实现
2021/09/25 Oracle