视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
CSS3 动画卡顿性能优化的完美解决方案
2020-11-27 22:07:25 责编:小采
文档

为什么会卡顿?

有一个前提必须要提,前端开发者们都知道,浏览器是单线程运行的。但是我们要明确以下几个概念:单线程,主线程和合成线程。
虽然说浏览器执行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;
}

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

而使用 transform:scale 实现

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

此时流程如下:

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

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

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

rendering 和 painting 所花的时间如下:

再来看看用 transform:scale

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 动画卡顿方案

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

总结

下载本文
显示全文
专题