简化代码学习jquery动画源码
2010-8-18
效果见这里:http://bangswork.googlecode.com/svn/trunk/lab/effect/index.html
jquery的动画总体思路是:
有一个fx类专门处理动画,fx的各个实例共享一个timers数组和一个setInterval。对每个传进来的dom的每个属性值都新建一个fx实例去处理,一个fx实例对应一个dom的一个属性的变化。fx里有个step函数,可以计算出当前这个时刻这个属性要达到什么值。
这个step会通过共用的一个setInterval每13毫秒执行一次,这就可以使得它行成动画。另外如果浏览器速度太慢无法达到13毫秒执行一次step,动画也会按时完成,因为是根据当前系统时间计算属性要达到的值的。
每一个fx实例的step都会放进timers数组,实际上setInterval是持续执行timers里的每一个函数,这样只用一个setInterval就让众多属性“一起动”了。step里判断到超过了动画运行的时间,就会返回false让它从timers里移除,timers为空时clearInterval。
下面简单实现这整个过程:
(function (window) {
var timerId,
timers = [],
rfxnum = /^([+-]=)?([\d+-.]+)(.*)$/,
effect = {
animate : function (elems, prop, duration) {
for ( var i = 0, l = elems.length; i < l; i ++ ) {
for ( var p in prop ) {
var e = new effect.fx ( elems[i], p, prop[p], duration ),
parts = rfxnum.exec(prop[p]),
start = e.cur(), //获得属性初始状态
end = parseFloat( parts[2] ),
unit = p == "opacity" ? "" : parts[3] || "px";
e.custom( start, end, unit );
}
}
},
now : function () {
return new Date().getTime();
},
stop : function (elems) {
//stop清除timers里于传进的elems有关的元素
for ( var i = 0, l = elems.length; i < l; i ++ ) {
for ( var j = timers.length - 1; j >= 0; j-- ) {
if ( timers[j].elem === elems[i] ) {
timers.splice(j, 1);
}
}
};
},
easing: {
//线性变化公式
linear: function( p, n, firstNum, diff ) {
return firstNum + diff * p;
},
//缓动公式
swing: function( p, n, firstNum, diff ) {
return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
}
},
fx : function (elem, name, val, duration) {
this.val = val;
this.duration = duration;
this.elem = elem;
this.name = name;
}
}
effect.fx.prototype = {
cur : function () {
return parseFloat( this.elem.style[this.name] );
},
custom : function (from, to, unit) {
this.startTime = effect.now();
this.start = from;
this.end = to;
this.unit = unit;
this.now = this.start;
this.state = this.pos= 0; //如果是线性变化 state会一直等于pos 如果是缓动 会通过state计算出pos 即此刻应该达到几分之几的状态
//为了让step里的this指向正确,创建一个闭包
var self = this;
function t() {
return self.step();
}
//指明这个函数对应的dom元素,在stop里可以根据这个元素指定特定dom停止动画
t.elem = this.elem
timers.push(t)
if ( !timerId ) {
timerId = setInterval( effect.fx.tick, 13 );
}
},
step : function () {
var t = effect.now(),
done = true;
if ( t >= this.duration + this.startTime ) {
this.now = this.end;
this.state = this.pos = 1;
this.update();
return false;
} else {
var n = t - this.startTime;
this.state = n / this.duration;
//如果只是线性渐变,这里可以直接this.pos = this.state
this.pos = effect.easing.swing( this.state, n, 0, 1, this.duration);
this.now = this.start + ( (this.end - this.start) * this.pos );
this.update();
return true;
}
},
update : function () {
this.elem.style[this.name] = this.now + this.unit;
}
}
effect.fx.tick = function () {
for ( var i = 0; i < timers.length; i ++ ) {
!timers[i]() && timers.splice( i--, 1 );
}
!timers.length && effect.fx.stop();
}
effect.fx.stop = function () {
clearInterval( timerId );
timerId = null;
}
window.effect = effect;
})(window);
这个是把整个过程尽量简化了,jquery做的包括很多细节处理,例如,不同形式的传参方式统一成包装一个对象,处理各种show hide,处理元素单位确保不出错,其中获取元素的width和height的值比较复杂,放在css模块里。还有支持队列的特性,默认是各种动画同步执行,如果需要按队列排序执行就用到queue方法。还有很多,这些全部加起来,源码就变得挺复杂了~
为什么不把iQuery做为jQuery的扩展呢?