http://www.web008.net

css渲染流程及动画深入理解和优化方案,无线页面动画优化实例

二、浏览器的页面渲染流水线

我们的代码是如何一步步的渲染成页面的呢?

美高梅手机版 1

  • JavaScript。一般来说,我们使用JavaScript来实现一些页面逻辑,但偶尔我们也可能会使用JavaScript来实现一些视觉变化的效果。比如用jQuery的animate函数做一个动画、或者往页面里添加一些DOM元素等。当然,现在更可能的是使用CSS Animations, Transitions和Web Animation API。
  • 计算样式(Style)。这个过程是通过样式文件中的CSS选择器,对每个DOM元素匹配对应的CSS样式。
  • 布局(Layout)。上一步确定了每个DOM元素的样式规则,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。因此对于浏览器来说,布局过程是经常发生的。
  • 绘制(Paint)。绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。
  • 渲染层合并(Composite)。由上一步可知,对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

看起来每个页面都会经历这样的几个过程,然而我们其实可以使用一些技巧,帮助浏览器跳过某些步骤,而缩短他的工作时间。

1.五个步骤都消耗了时间

当我们在js中改变了某个DOM元素的layout时,那么浏览器就会检查页面中的哪些元素需要重新布局,然后对页面激发一个reflow过程以完成页面的重新布局。被reflow的元素,接下来就一定会再次经过Paint和Composite这两个过程,以渲染出最新的页面。

 

2.跳过layout这一步

当我们只修改了一个DOM元素的paint only属性的时候,比如background-image/color/box-shadow等。这个时候不会触发layout,浏览器在完成样式的计算之后就会跳过layout的过程,就只Paint和Composite了。

 

3.跳过layout和paint这两步

如果你修改一个非样式且非绘制的CSS属性,那么浏览器会在完成样式计算之后,跳过布局和绘制的过程,直接Composite。

我们尝试下使用transform动画来尽可能的达到这种效果。

 

6.性能检测工具:Timeline或Performance 检测动画性能

对比一下两个动画

//1.不使用transform:引起重排和重绘

@keyframes run-around{

    0%{top: 0;left: 0;}

    25%{top: 0;left: 200px;}

    50%{top: 200px;left: 200px;}

    75%{top: 200px;left: 0;}

    100%{top: 0;left: 0;}

}

美高梅手机版 2

//2.使用transform:不引起重排和重绘

@keyframes run-around{

    0%{transform: translate(0,0);}

    25%{transform: translate(200px,0);}

    50%{transform: translate(200px,200px);}

    75%{transform: translate(0,200px);}

    100%{transform: translate(0,0);}

}

//3.矩阵动画: 更高效

@keyframes run-around{

    0%{transform: matrix(1, 0, 0, 1, 0, 0);}   

    25%{transform: matrix(1, 0, 0, 1, 200, 0);} 

    50%{transform: matrix(1, 0, 0, 1, 200, 200);}

    75%{transform: matrix(1, 0, 0, 1, 0, 200);} 

    100%{transform: matrix(1, 0, 0, 1, 0, 0);}

}

美高梅手机版 3

1.在整个动画的每一帧中,浏览器都要去重新布局,绘制页面,并把最新的位图对象加载到GPU2.根据定义,CSS的transform属性不会改变元素的布局,也不会影响到其周围的元素。它把元素当做一个整体看待——缩放整个元素、旋转整个元素或者移动整个元素。 浏览器只需在动画开始的时候生成这个元素的位图对象,并把它传递给GPU。在这之后,浏览器无需再做任何重新布局,绘制页面以及传递位图对象的操作了,相反,浏览器可以利用GPU擅长的绘制的特点来快速的在不同的位置,旋转或缩放同一个位图对象

transform: 节省了CPU进行Layout、Paint的时间((跳过),节省了CPU向GPU传输位图的时间

最后用矩阵matrix来代替转换成transform这样子又避免了动画开始的时候生成这个元素的位图图像,把性能做到极致

无线页面动画优化实例

2016/04/20 · CSS · 无线

原文出处: 大额_skylar(@大额大额哼歌等日落)   

无线页面本就分秒必争,更不用说当我们在无线页面中使用动画的时候。不管是css动画还是canvas动画,我们都需要时刻小心着,并且有必要掌握页面性能的基本分析方法。

既然我们的目标是优化,那么就与浏览器的一些渲染和执行机制有关,更好的迎合浏览器的行为方式,才可以让我们的动画流畅而优美。

没错,浏览器是老大,全听它的。

美高梅手机版 4

 

4.不要以为单独的层是万能的,单独层内部的元素触发重排、重绘的条件,一样会只重排、重绘这一层

五、分析你的无线页面

我们还是借助这个例子,[圆弧progress][Source Code] 简单的看下如何分析无线页面的性能。

这里的实现思路是这样的:

1 - 确定圆弧的起始弧度(0.75PI)和终止弧度(根据当前分值占上限分值的比例计算,最大为2.25PI); 2 - 随着时间的增长逐帧绘制终点位置 requestAnimationFrame(draw); 3 - 根据每一帧的终点位置的 cos 和 sin 值得到跟随的圆圈坐标并绘制;

1
2
3
1 - 确定圆弧的起始弧度(0.75PI)和终止弧度(根据当前分值占上限分值的比例计算,最大为2.25PI);
2 - 随着时间的增长逐帧绘制终点位置 requestAnimationFrame(draw);
3 - 根据每一帧的终点位置的 cos 和 sin 值得到跟随的圆圈坐标并绘制;

但当然,实现完成只是走了第一步,我们来借助Chrome Timeline来分析一下这个简单的页面。

美高梅手机版 5

 

  1. 看一下帧率,在进度动画进行的时候,看起来帧率不错,没有产生掉帧的现象,说明每一帧的耗时都还ok,我的动画基本不会卡顿;
  2. 在函数的执行和调用那一栏中,可能有问题的部分右上角会被标红,还可以查看可能存在问题的细节;这里提示我页面强制重排了,仔细观察下面的 Bottom-up tab 中可以定位到具体的代码。

使用Timeline就可以看到页面的几种指标,帧率,js执行等等。就可以针对出现问题的帧下手优化。

在分析页面性能的时候,严重推荐阅读:[] .timeline的详细使用说明,它真的很强大,能帮助我们分析到页面的各个方面的问题。

1 赞 1 收藏 评论

美高梅手机版 6

方案二: 分离读写,减少Layout

在每一帧先做批量的读操作,再批量运行写操作

fastdom.js 3.1.使用读写分类的策略来优化

四、从css到canvas,使用requestAnimationFrame

现在css的动画越来越好用,也能满足越来越多的需求。但在某些复杂的需求中我们可能还是要求助于js。

比如说我这里实现的一个半圆的动画:[半圆progress] [Source Code]。看起来使用css动画就完全可以满足我的需求,但是当需求变化的时候,我们也只能拥抱变化了。

 

**使用requestAnimationFrame**

[圆弧progress][Source Code] 这里用canvas实现了自定义弧度圆弧的增长动画。

这里我们借助这个动画效果看一下是如何使用canvas和requestAnimationFrame来实现流畅的逐帧动画的。

window.requestAnimationFrame 是一个专门为动画而生的 web API 。它通知浏览器在页面重绘前执行你的回调函数。通常来说被调用的频率是每秒60次。

假设我们的页面上有一个动画效果,如果我们想保证每一帧的顺利绘制,那么我们就需要requestAnimationFrame来保证我们的绘制时机了。

很多框架和示例代码都是用setTimeoutsetInterval来实现页面中的动画效果,比如jQuery中的animation。这种实现方式的问题是,你在setTimeoutsetInterval中指定的回调函数的执行时机是无法保证的。它将在这一帧动画的_某个时间点_被执行,很可能是在帧结束的时候。这就意味这我们可能失去这一帧的信息。

美高梅手机版 7

 

**requestAnimationFrame的其他高能用法**

根据requestAnimationFrame的特性,其实我们还可以在很多别的想不到的地方来一显身手。

  • 动画:也是它的主要用途,它将我们动画的执行时机和执行频率交由浏览器决定,以得到更好的性能;
  • 函数节流:requestAnimationFrame 的执行频率(一帧)是16.67ms,利用这一个特征就可以做到函数节流,避免高频事件在一帧内做多余的无用功的函数执行,例:
JavaScript

var $box = $('#J_Test'), $point = $box.find('b');
$box.on('mouseenter',function(e){ requestAnimationFrame(function(){
$point.css({ top : e.pageY, left : e.pageX }) }); });

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6d19187bb345735832-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bb345735832-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bb345735832-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bb345735832-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bb345735832-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bb345735832-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bb345735832-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bb345735832-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bb345735832-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bb345735832-10">
10
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6d19187bb345735832-1" class="crayon-line">
var $box = $('#J_Test'),
</div>
<div id="crayon-5b8f6d19187bb345735832-2" class="crayon-line crayon-striped-line">
      $point = $box.find('b');
</div>
<div id="crayon-5b8f6d19187bb345735832-3" class="crayon-line">
$box.on('mouseenter',function(e){
</div>
<div id="crayon-5b8f6d19187bb345735832-4" class="crayon-line crayon-striped-line">
  requestAnimationFrame(function(){
</div>
<div id="crayon-5b8f6d19187bb345735832-5" class="crayon-line">
      $point.css({
</div>
<div id="crayon-5b8f6d19187bb345735832-6" class="crayon-line crayon-striped-line">
          top : e.pageY,
</div>
<div id="crayon-5b8f6d19187bb345735832-7" class="crayon-line">
          left : e.pageX
</div>
<div id="crayon-5b8f6d19187bb345735832-8" class="crayon-line crayon-striped-line">
      })
</div>
<div id="crayon-5b8f6d19187bb345735832-9" class="crayon-line">
  });
</div>
<div id="crayon-5b8f6d19187bb345735832-10" class="crayon-line crayon-striped-line">
});
</div>
</div></td>
</tr>
</tbody>
</table>
  • 分帧初始化:同样利用一帧的执行时间将模块的初始化或渲染函数分散到不同的帧中来执行,这样每个模块都有16.67ms的执行时间,而不是一股脑的堆在那里等着执行;
JavaScript

var rAF = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame || function(c) { setTimeout(c, 1
/ 60 * 1000); }; function render() {
self.$container.html(itemHtml);
self.$container.find('.J_LazyLoad').lazyload(); } rAF(render);

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bf045103677-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bf045103677-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bf045103677-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bf045103677-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6d19187bf045103677-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f6d19187bf045103677-11">
11
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6d19187bf045103677-1" class="crayon-line">
var rAF = window.requestAnimationFrame ||  window.webkitRequestAnimationFrame || 
</div>
<div id="crayon-5b8f6d19187bf045103677-2" class="crayon-line crayon-striped-line">
        function(c) {
</div>
<div id="crayon-5b8f6d19187bf045103677-3" class="crayon-line">
            setTimeout(c, 1 / 60 * 1000);
</div>
<div id="crayon-5b8f6d19187bf045103677-4" class="crayon-line crayon-striped-line">
        };
</div>
<div id="crayon-5b8f6d19187bf045103677-5" class="crayon-line">
 
</div>
<div id="crayon-5b8f6d19187bf045103677-6" class="crayon-line crayon-striped-line">
    function render() {
</div>
<div id="crayon-5b8f6d19187bf045103677-7" class="crayon-line">
       self.$container.html(itemHtml);
</div>
<div id="crayon-5b8f6d19187bf045103677-8" class="crayon-line crayon-striped-line">
       self.$container.find('.J_LazyLoad').lazyload();
</div>
<div id="crayon-5b8f6d19187bf045103677-9" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6d19187bf045103677-10" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f6d19187bf045103677-11" class="crayon-line">
    rAF(render);
</div>
</div></td>
</tr>
</tbody>
</table>

###两个问题

-- 开始绘制的时机

-- 绘制一帧的时间(16.7ms最完美)

一、设备刷新率(帧率)

我们想让页面变快,想让动画流畅,我们需要先了解一下是什么在影响着我们的感知。

页面运行在设备的浏览器中,现在市面上的移动设备的刷新频率大多是60次/秒(帧率)。所以给浏览器渲染每一帧的画面的时间应该是(1s/60=16.67ms)。

但实际上,浏览器并不是把功夫全花在为我们渲染页面上,他还需要做一些额外的工作,比如渲染队列的管理和不同线程的切换等等。所以,单纯的浏览器渲染工作留给我们的时间大约也就是10ms左右,当我们在每一帧所做的渲染操作大于这个时间的时候,比较直观的表现就是页面卡顿,动画卡顿。

当我们使用css animation完成动画时,这一点看起来没有那么重要,因为浏览器会为我们handle一些事情。但是当我们需要使用js比如canvas来实现流畅的逐帧动画时,需要牢记这个有限的时间,它很重要。

总结

美高梅手机版 8

三、使用transform实现动画

我们可能经常需要做一些动画,比如在做某些揭秘或者新手引导的效果时,会需要做一些将内容移入移出的操作。

当然可能第一个想到的就是 css transition 只要过渡一下 left 值或者 bottom 的值就可以了。效果或许很快就会实现,但是当我们在一个页面频繁的做着这样的移入移出操作时,细心地我们放在手机中(6P)看一看,动画并不会很流畅,尤其是在某些低端机型上。

我们换用 transform 来实现相同的效果:

transition: left 2s ease-in-out; ---> transition: transform 2s ease-in-out; left: xxx; ---> transform: translate3d(xxx, yyy, zzz);

1
2
transition: left 2s ease-in-out;  ---> transition: transform 2s ease-in-out;  
left: xxx; ---> transform: translate3d(xxx, yyy, zzz);

原因在于:

  • 简单的说页面的绘制并不是在单层的画面里完成的,这其中有渲染层合成层等概念。对 opacity 和 transform 应用了 CSS 动画的渲染层、有 3D 或者 perspective transform 的 CSS 属性的渲染层等满足一些条件的渲染层被称为合成层;
  • 合成层有自己的渲染上下文,并且交由 GPU 处理,比 CPU 要快;
  • 当页面需要重绘时,合成层的元素只会重绘自己层内的元素,而非整个页面;

优化过后再放在设备里查看,可以感受到效果明显的提升。其实这里就做到了上面提到的,节省了layout和paint。

1.webkit渲染html+css

1-1.获取DOM 分割层 

1-2.计算CSS样式 

1-3.重排,放置dom的位置(layout) 

1-4.节点填充 重绘(paint)

 1-5.GPU生成纹理展现到页面(元素偏移、缩放)

 1-6.GPU参与网页合成层(compsite) => 屏幕最终图像 

【DOM子树渲染层(RenderLayer) -> RenderObject(元素) -> GraphicsContext】 

【Compositor -> 渲染层子树的图形层(GraphicsLayer) -> RenderLayer -> RenderObject】 【Compositor将所有的拥有compositing layer 进行合成,合成过程GPU进行参与。 合成完毕能够将纹理映射到一个网格几何机构之上,纹理能够以很低代价映射到不同的位置,而且还能够以很低的代价通过把他们应用到一个非常简单的矩形网格中进行变形,这就是 3D CSS 实现原理。】

 【GPU参与: CSS3D、webgel、transform、硬件加速】 

【硬件加速: ①.Texture,即CPU传输到GPU的一个BitMap位图 ②GPU能快速对Texture进行偏移、缩放、旋转、修改透明度等操作 开启硬件加速,让动画元素独立创建一个层,例如transform:translateZ(0) 】 

【Composite:GPU也是有限度的,不要滥用GPU资源生成不必要的Layer,留意意外生成的Layer】

 美高梅手机版,总结: 浏览器渲染: Load、Layout、Paint、Composite Layers 修改不同的CSS属性会触发不同阶段 触发的阶段越前,渲染的代价越高

2.网页就像搭积木:一旦积木位置移动-重排,上色-重绘

2.1.网页生成时,至少会渲染一次,用户访问过程中,还不会不断重新渲染(修改DOM、修改样式表、用户事件)

2.2.重绘不一定引起重排,但重排一定会引起重绘

2.3.重排发生原因:页面的初始化、引起的盒子变化、添加或者删除可见的DOM元素、元素位置改变、元素尺寸改变、元素内容改变(例如:一个文本被另一个不同尺寸的图片替代)、页面渲染初始化(无法避免)、浏览器窗口尺寸改变、读取CSS相关属性也会触发重排 

美高梅手机版 9

2.3.1.尽量不触发Layout、使用transform代替top,left的动画

2.4.重绘:外观改变:当修改border-radius,box-shadow,color,backgroud等展示相关属性时候,会触发重绘、在经常paint的区域,要避免代价太高style、、不要用gif图,或者在不需要时,display:none,减少绘制区域,为引起大范围Paint的元素生成独立的Layer(比如将动画部分设置position:absolute)

那么我们怎么避免重排和重绘给它们进行优化呢?

浏览器会把要引起重绘与重排的操作都塞到主线程队列里面,正准备执行优化后队列的时候,如果你做了一个读取width的操作,浏览器会全部放弃之前的优化,造成性能急剧下降

网页的分层机制

一个网页是由多个层展示的,然后再将这些层合并成一个层,当dom或者样式发生变化时,GPU能够缓存一些不变的内容,将要变化的层与缓存层再合成,提高渲染效率,因此在做动画时要让GPU参与进来,提高动画性能

郑重声明:本文版权归美高梅163888所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。