2015年2月8日星期日

jQuery源码分析系列(40): 动画设计 - 【Aaron】

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
jQuery源码分析系列(40): 动画设计 - 【Aaron】  阅读原文»

前言

jQuery动画是通过animate这个API设置执行的,其内部也是按照每一个animate的划分封装了各自动画组的行为,

包括数据过滤、缓动公式、一些动画默认参数的设置、元素状态的调整、事件的处理通知机制、执行等等

换句话说,我们可以把animate看作一个对象,对象封装自己的一系列属性与方法。

jQuery可以支持连续动画,那么animate与animate之间的切换就是通过队列.queue,这个之前就已经详细的解释过了

动画的参数

jQuery的内部的方法都是针对API的处理范围设计的

我们看看Animation方法的支持情况:

.animate( properties [, duration ] [, easing ] [, complete ] )
.animate( properties, options )
  • 区别就与第二组数据的传递了,options是支持对象传参
  • properties参数就是写一个CSS属性和值的对象,动画都是涉及变化的,那么什么值才能变化?
  • 理论上来说有数值的属性都是可以变化的,width, height或者left可以执行动画,但是background-color不能,但是也不是绝对的,主要看数据的解析度,可以用插件支持
  • 除了样式属性, 一些非样式的属性,如scrollTopscrollLeft,以及自定义属性,也可应用于动画
  • 除了定义数值,每个属性能使用'show', 'hide', 和 'toggle'。这些快捷方式允许定制隐藏和显示动画用来控制元素的显示或隐藏。为了使用jQuery内置的切换状态跟踪,'toggle'关键字必须在动画开始前给定属性值

简单的来说,就是把一对的参数丢大animate方法里面,然后animate就开始执行你参数规定的动画了,

那么动画每执一次就会通过回调通知告诉开发者,具体有complete/done/fail/always/step接口等等

理解定义

<img id="book" alt="" width="100" height="123"
style
="background:red;opacity:1;position: relative; left: 500px;" />

book.animate({
opacity:
0.25,
left:
'50',
height:
'toggle'
}, {
duration :
1000,
specialEasing: {
height:
'linear'
},
step: function(now, fx) {
console.log(
'step')
},
progress:function(){
console.log(
'progress')
},
complete:function(){
console.log(
'动画完成')
}
})

首先,动画的参数都是最终值都是相对数据

如上img元素的起始

opacity是1,那么通过动画改成成0.25

left是500,那么通过动画改成成50

height为'toggle' 意味着如果是隐藏与显示的自动切换

step:是针对opacity/left/height各自动画,每次改变通知三次

progress 是把opacity/left/height看成一组了,每次改变只通知一次

动画的原理

jQuery动画的原理还是很简单的,靠定时器不断的改变元素的属性

我们模拟下animate的大致实现

让元素执行一个2秒的动画到坐标为left 50的区域

animate({ left: '50',
duration:
'2000'
}

按照常规的思路,我们需要3个参数

  • 动画开始位置
  • 动画的结束位置
  • 动画的运行时间

思路一:等值变化

我们在animate内部还需要计算出当然元素的初始化布局的位置(比如500px),那么我们在2秒的时间内需变换成50px,也就是运行的路劲长就是500-50 = 450px

那么算法是不是呼之欲出了?

每毫秒移动的距离 pos = 450/2000 = 0.225px

每毫秒移动left = 初始位置 (+/-) 每毫秒递增的距离(pos * 时间)

这样算法我们放到setInterval就会发现错的一塌糊涂,我们错最本质的东西:JS是单线程,定时器都是排队列的,理论上也达不到1ms绘制一次dom

所以每次产生的这个下一次绘制的时间差根本不是一个等比的,所以我们按照线性的等值递增是有误的

function animate(elem, options){
//动画初始值
var start = 500
//动画结束值
var end = options.left
//动画id
var timerId;
var createTime = function(){
return (+new Date)
}
var startTime = createTime();
//需要执行动画的长度
var anminLength = start - end;
//每13毫秒要跑的位置
var pos = anminLength/options.time * 13
var pre = start;
var newValue;
function tick(){
if(createTime() - startTime < options.time){
newValue
= pre - pos
//动画执行
elem.style['left'] = newValue + 'px';
pre
= newValue
}
else{
//停止动画
clearInterval(timerId);
timerId
= null;
console.log(newValue)
}
}
//开始执行动画
var timerId = setInterval(tick, 13);
}

思路一实现: