【前端优化】动画几种实现方式总结和性能分析

爱你不变 2019-07-01

备注:没整理格式,抱歉

动画实现的几种方式:性能排序
js < requestAnimationFrame <css3< Canvas
js实现方式:
1.setTimeout 自身调用 eg1
2.setInterval 调用 eg2
setTimeout的定时器值推荐最小使用16.7ms的原因(16.7 = 1000 / 60, 即每秒60帧)
为什么倒计时动画一定要用setTimeout而避免使用setInterval-------两者区别及setTimeout引发的js线程讨论
1.js线程讨论
1.1 为什么:单线程是JavaScript的一大特性。
JavaScript是浏览器用来与用户进行交互、进行DOM操作的,这也使得了它必须是单线程这一特性。比如你去修改一个元素的DOM,同时又去删除这个元素,那么浏览器应该听谁的?
1.2 js单线程工作机制是:当线程中没有执行任何同步代码的前提下才会执行异步代码

var t = true;
window.setTimeout(function (){
    t = false;},1000);
while (t){}
alert('end')

JavaScript引擎是单线程运行的,浏览器只有一个线程在运行JavaScript程序
1.3 浏览器工作基本原理
一、浏览器的内核是多线程的,内核制控下保持同步,
至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程(http请求线程等)

  1. javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。
  2. GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。

但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  1. 事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。异步事件:如setTimeOut、浏览器内核的其他线程如鼠标点击、AJAX异步请求等,(当线程中没有执行任何同步代码的前提下才会执行异步代码)

也就是说即使setTimeout为0,他也是等js引擎的代码执行完之后才会插入到js引擎线程的最后执行。
1.4 JavaScript中任务,一种是同步任务,一种是异步任务。
同步任务:各个任务按照文档定义的顺序一一推入"执行栈"中,当前一个任务执行完毕,才会开始执行下一个任务。
异步任务:各个任务推入"任务队列"中,只有在当前的所有同步任务执行完毕,才会将队列中的任务"出队"执行。(注:这里的异步任务并不一定是按照文档定义的顺序推入队列中)
//只有用户触发点击事件才会被推入队列中(如果点击时间小于定时器指定的时间,则先于定时器推入,否则反之)
1.5 "任务队列是什么?异步任务通常包括哪些?"
任务队列(event loop):你可理解为用于存放事件的队列,当执行一个异步任务时,就相当于执行任务的回调函数。
通常io(ajax获取服务器数据)、用户/浏览器自执行事件(onclick、onload、onkeyup等等)以及定时器(setTimeout、setInterval)都可以算作异步操作。

先来看一段代码来理解一下

console.log("1");
setTimeout(function(){
console.log("2");
},1000);
console.log("3");
setTimeout(function(){
console.log("4");
},0);
输出结果: 1->3->4->2.

那么在来看你这段代码。

var t = true;
window.setTimeout(function (){
t = false
},1000);
while (t){}
alert('end');
1.6 setTimeOut的讨论

参数
描述
code
必需。要调用的函数后要执行的 JavaScript 代码串。
millisec
必需。在执行代码前需等待的毫秒数。

提示:setTimeout() 只执行 code 一次。如果要多次调用,请使用 setInterval() 或者让 code 自身再次
原理:setTimeout调用的时候,JavaScript引擎会启动定时器timer,当定时器时间到,就把该事件放到主事件队列等待处理。
注意:浏览器JavaScript线程空闲的时候才会真正执行 ep3
millisec参数有什么用?
那么问题来了。setTimeout(handler,0)和setTimeout(handler,100)在单独使用时,好像并没有区别。(中间执行的代码处理时间超过100ms时)
millisec一般在多个setTimeout一起使用的时,需要区分哪个先加入到队列的时候才有用,否则都可以设置成setTimeout(handler,0)
1.7 SetTimeout 与 setInterval的区别

setTimeout(function(){
/* 代码块... */
setTimeout(arguments.callee, 10);
}, 10);
setInterval(function(){
/*代码块... */
}, 10);

setTimeout递归执行的代码必须是上一次执行完了并间格一定时间才再次执行
比仿说: setTimeout延迟时间为1秒执行, 要执行的代码需要2秒来执行,那这段代码上一次与下一次的执行时间为3秒. 而不是我们想象的每1秒执行一次.
setInterval是排队执行的
比仿说: setInterval每次执行时间为1秒,而执行的代码需要2秒执行, 那它还是每次去执行这段代码, 上次还没执行完的代码会排队, 上一次执行完下一次的就立即执行, 这样实际执行的间隔时间为2秒
这样的话在我看来, 如果setInterval执行的代码时间长度比每次执行的间隔段的话,就没有意义,并且队伍越来越长,内存就被吃光了.如果某一次执行被卡住了,那程序就会被堵死
巨坑无比的setInterval
定时器的代码可能在代码还没有执行完成再次被添加到队列,结果导致循环内的判断条件不准确,代码多执行几次,之间没有停顿。
JavaScript已经解决这个问题,当使用setInterval()时,仅当没有该定时器的其他代码实例时才将定时器代码插入队列。这样确保了定时器代码加入到队列的最小时间间隔为指定间隔

  1. 某些间隔会被跳过

2.多个定时器的代码执行之间的间隔可能比预期要小

大前端团队 > 前端动画实现 > image2017-11-28 14:24:25.png
5处,创建一个定时器
205处,添加一个定时器,但是onclick代码没执行完成,等待
300处,onclick代码执行完毕,执行第一个定时器
405处,添加第二个定时器,但前一个定时器没有执行完成,等待
605处,本来是要添加第三个定时器,但是此时发现,队列中有了一个定时器,被跳过
等到第一个定时器代码执行完毕,马上执行第二个定时器,所以间隔会比预期的小。
二 CSS3动画
1.tansition

transition-property 要运动的样式 (all || [attr] || none)
transition-duration 运动时间
transition-delay 延迟时间
transition-timing-function 运动形式
ease:(逐渐变慢)默认值
linear:(匀速)
ease-in:(加速)
ease-out:(减速)
ease-in-out:(先加速后减速)
cubic-bezier 贝塞尔曲线( x1, y1, x2, y2 ) http://matthewlein.com/ceaser/

transition的完整写法如下

img { 
   transition: 1s 1s height ease;
}

单独定义成各个属性。

img{ 
    transition-property: height;
    transition-duration: 1s;
    transition-delay: 1s;
    transition-timing-function: ease;
}

/可以多个动画同时运动/用逗号隔开

transition:1s width,2s height,3s background;

/可以在动画完成时间之后添加动画延迟执行的时间/

transition:1s width,2s 1s height,3s 3s background;

过渡完成事件

// Webkit内核: 
obj.addEventListener('webkitTransitionEnd',function(){},false);
// firefox: 
obj.addEventListener('transitionend',function(){},false);
/*tansition动画发生在样式改变的时候*/
function addEnd(obj,fn) ---封装适应与各个浏览器的动画结束
{   
    //动画执行完执行该函数
    obj.addEventListener('WebkitTransitionEnd',fn,false);
    obj.addEventListener('transitionend',fn,false); //标准
}
addEnd(oBox,function(){
    alert("end");   
});
// 面临两个bug:1.tansition中有多个动画时,每个执行完,都会有一个结束弹出
           // 2.发生重复调用的情况--需要移除
//移除动画执行完的操作
function removeEnd(obj,fn)
}
    obj.removeEventListener('transitionend',fn,false);
    obj.removeEventListener('WebkitTransitionEnd',fn,false);
{

使用注意

(1)不是所有的CSS属性都支持transition
http://oli.jp/2010/css-animatable-properties/
http://leaverou.github.io/animatable/
(2)transition需要明确知道,开始状态和结束状态的具体数值,才能计算出中间状态
transition的局限
transition的优点在于简单易用,但是它有几个很大的局限。
(1)transition需要事件触发,所以没法在网页加载时自动发生。
(2)transition是一次性的,不能重复发生,除非一再触发。
(3)transition只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态。
(4)一条transition规则,只能定义一个属性的变化,不能涉及多个属性。

CSS Animation就是为了解决这些问题而提出的。
2.transform

rotate() 旋转函数 取值度数 deg 度数 -origin 旋转的基点
skew() 倾斜函数 取值度数
skewX()
skewY()
scale() 缩放函数 取值 正数、负数和小数
scaleX()
scaleY()
translate() 位移函数
translateX()
translateY()

Transform 执行顺序问题 — 后写先执行

-webkit-transform:rotate(360deg);
旋转原点可以是关键字+像素位置:相对于左上角作为零点:正为下,右
-webkit-transform-origin:right bottom;
-webkit-transform-origin:200px 200px;
一个transform可以有多个值:
 -webkit-transform:rotate(360deg) scale(0.2);
-webkit-transform:skewX(45deg);
-webkit-transform:skewY(45deg); 
-webkit-transform:skew(15deg,30deg);

3.Animation 关键帧——keyFrames
只需指明两个状态,之间的过程由计算机自动计算
关键帧的时间单位
数字:0%、25%、100%等
字符:from(0%)、to(100%)
格式

@keyframes 动画名称
{
动画状态
}
@keyframes miaov_test
{
from { background:red; }
to { background:green; }
}

可以只有to
必要属性
animation-name 动画名称(关键帧名称)
animation-duration 动画持续时间
属性:
animation-play-state 播放状态( running 播放 和paused 暂停 )
animation-timing-function 动画运动形式
linear 匀速。
ease 缓冲。
ease-in 由慢到快。
ease-out 由快到慢。
ease-in-out 由慢到快再到慢。
cubic-bezier(number, number, number, number): 特定的贝塞尔曲线类型,4个数值需在[0, 1]区间内
animation-delay 动画延迟只是第一次
animation-iteration-count 重复次数/infinite为无限次
animation-direction 播放前重置/动画是否重置后再开始播放
alternate 动画直接从上一次停止的位置开始执行
normal 动画第二次直接跳到0%的状态开始执行
reverse
alternate-reverse
animation-fill-mode
forwards 让动画保持在结束状态
none:默认值,回到动画没开始时的状态。
backwards:让动画回到第一帧的状态。
both: 根据animation-direction(见后)轮流应用forwards和backwards规则。
animation-play-state
paused
running
动画播放过程中,会突然停止。这时,默认行为是跳回到动画的开始状态,想让动画保持突然终止时的状态,就要使用animation-play-state属性
大前端团队 > 前端动画实现 > image2017-11-28 14:29:8.png
animation也是一个简写形式

div:hover { 
    animation: 1s 1s rainbow linear 3 forwards normal;
}

分解成各个单独的属性

div:hover { 
    animation-name: rainbow;
    animation-duration: 1s;
    animation-timing-function: linear;
    animation-delay: 1s;
    animation-fill-mode:forwards;
    animation-direction: normal;
    animation-iteration-count: 3;
}

Animation与Js的结合
通过class,在class里加入animation的各种属性
直接给元素加-webkit-animation-xxx样式
animation的问题
写起来麻烦
没法动态改变目标点位置
animation的函数:

obj.addEventListener('webkitAnimationEnd', function (){}, false);

实例1:无缝滚动
animation的step
eg: http://dabblet.com/gist/1745856
animation-timing-function: steps(30, end)
1.什么时候使用:
animation默认以ease方式过渡,它会在每个关键帧之间插入补间动画,所以动画效果是连贯性的,除了ease,linear、cubic-bezier之类的过渡函数都会为其插入补间。但有些效果不需要补间,只需要关键帧之间的跳跃,这时应该使用steps过渡方式
大前端团队 > 前端动画实现 > image2017-11-28 14:29:45.png
线性动画: http://sandbox.runjs.cn/show/...
帧动画:http://sandbox.runjs.cn/show/...
2.step使用:
语法:
steps(number[, end | start])
参数说明:
number参数指定了时间函数中的间隔数量(必须是正整数)
第二个参数是可选的,可设值:start和end,表示在每个间隔的起点或是终点发生阶跃变化,如果忽略,默认是end。
大前端团队 > 前端动画实现 > image2017-11-28 14:30:37.png
横轴表示时间,纵轴表示动画完成度(也就是0%~100%)。
第一个图,steps(1, start)将动画分为1段,跳跃点为start,也就是说动画在每个周期的起点发生阶跃(即图中的空心圆 → 实心圆)。由于只有一段,后续就不再发生动画了。
第二个图,steps(1, end)同样是将动画分为1段,但跳跃点是end,也就是动画在每个周期的终点发生阶跃,也是图中的空心圆 → 实心圆,但注意时间,是在终点才发生动画。
第三个图,steps(3, start)将动画分为三段,跳跃点为start,动画在每个周期的起点发生阶跃(即图中的空心圆 → 实心圆)。在这里,由于动画的第一次阶跃是在第一阶段的起点处(0s),所以我们看到的动画的初始状态其实已经是 1/3 的状态,因此我们看到的动画的过程为 1/3 → 2/3 → 1 。
第四个图,steps(3, end)也是将动画分为三段,但跳跃点为end,动画在每个周期的终点发生阶跃(即图中的空心圆 → 实心圆)。虽然动画的状态最终会到达100%,但是动画已经结束,所以100%的状态是看不到的,因此我们最终看到的动画的过程是0 → 1/3 → 2/3。
https://idiotwu.me/study/timi...
steps第一个参数的错误的理解:
第一个参数 number 为指定的间隔数,即把动画分为 n 步阶段性展示,估计大多数人理解就是keyframes写的变化次数
@-webkit-keyframes circle { 0% {background-position-x: 0;} 100%{background-position-x: -400px;} }
@-webkit-keyframes circle { 0% {} 25%{} 50%{} 75%{} 100%{} }
如果有多个帧动画
@-webkit-keyframes circle { 0% {background-position-x: 0;} 50% {background-position-x: -200px;} 100%{background-position-x: -400px;} }
0-25 之间变化5次, 25-50之间 变化5次 ,50-75 之间变化5次,以此类推
应用:
Sprite 精灵动画 2D游戏
https://idiotwu.me/css3-runni...
4.3D转换
父容器:
transform-style(preserve-3d) 建立3D空间
Perspective 景深
Perspective- origin 景深基点
子元素:
Transform 新增函数
rotateX()
rotateY()
rotateZ()
translateZ()
scaleZ()
实例1:3D盒子
http://beiyuu.com/css3-animation
使用实例:
requestAnimationFrame
是什么
js的一个API
该方法通过在系统准备好绘制动画帧时调用该帧,从而为创建动画网页提供了一种更平滑更高效的方法
使用
var handle = setTimeout(renderLoop, PERIOD);
var handle = window.requestAnimationFrame(renderLoop);
window.cancelAnimationFrame(handle);
为什么出现
css:

  1. 统一的向下兼容策略 IE8, IE9之流
  2. CSS3动画不能应用所有属性 scrollTop值。如果我们希望返回顶部是个平滑滚动效果
  3. CSS3支持的动画效果有限 CSS3动画的贝塞尔曲线是一个标准3次方曲线

缓动(Tween)知识:
Linear:无缓动效果
Quadratic:二次方的缓动(t^2)
Cubic:三次方的缓动(t^3)
Quartic:四次方的缓动(t^4)
Quintic:五次方的缓动(t^5)
Sinusoidal:正弦曲线的缓动(sin(t))
Exponential:指数曲线的缓动(2^t)
Circular:圆形曲线的缓动(sqrt(1-t^2))
Elastic:指数衰减的正弦曲线缓动
超过范围的三次方缓动((s+1)t^3 – st^2)
指数衰减的反弹缓动
js:
1.延迟时间固定导致了动画过度绘制,浪费 CPU 周期以及消耗额外的电能等问题
2.即使看不到网站,特别是当网站使用背景选项卡中的页面或浏览器已最小化时,动画都会频繁出现
大前端团队 > 前端动画实现 > image2017-11-28 14:31:6.png
相当一部分的浏览器的显示频率是16.7ms
搞个10ms setTimeout,就会是下面一行的模样——每第三个图形都无法绘制
显示器16.7ms刷新间隔之前发生了其他绘制请求(setTimeout),导致所有第三帧丢失,继而导致动画断续显示(堵车的感觉),这就是过度绘制带来的问题

requestAnimationFrame 与setTimeout相似,都是延迟执行,不过更智能,跟着浏览器的绘制走,如果浏览设备绘制间隔是16.7ms,那我就这个间隔绘制;如果浏览设备绘制间隔是10ms, 我就10ms绘制,浏览器(如页面)每次要重绘,就会通知(requestAnimationFrame)
页面最小化了,或者被Tab切换当前页面不可见。页面不会发生重绘
兼容性
Android设备不支持,其他设备基本上跟CSS3动画的支持一模一样
https://developer.mozilla.org...

相关推荐