jQuery可以很容易地实现动态效果,常用的如show/hidden, fadeIn/fadeOut,slideDown/slideUp,要实现任意的动态效果,使用animate。由于所有的动态效果最终调用animate方法,我今天就主要介绍这个方法。
3865 animate: function( prop, speed, easing, callback ) {
3866 var optall = jQuery.speed(speed, easing, callback);
3867
3868 return this[ optall.queue === false ? "each" : "queue" ](function(){
// 中间省略好多行...
3920 });
animate接受四个参数,第一个参数是是需要动态改变的样式属性及其最终值,例如{width: "70%", fontSize:"3em"}表示宽度会逐渐缩小到原来的70%,而字体大小会逐渐变到3em,样式属性必须能接收数字值,例如不能动态改变backGround样式。第二个参数是动态效果的持续时间,第三个参数是动态效果是线性变化(linear,匀速变化)还是摆动变化(swing,像单摆那样运动,先慢后快)。第四个参数是动态效果停止时的回调函数。第3866行的后三个参数合并到一个对象中,方便以后获取。有人可能要问,直接从参数中获取不是更方便吗?两个原因,一是后三个参数都是可选的,如果最后一个参数是函数,它总是代表回调函数,当调用animate({wdith:150}, function() {...})时,speed参数会得到回调函数,但实际上它应该由callback参数访问,经过jQuery.speed函数正规化后,回调函数总由optall.complete访问。另外,为了方便用户操作,speed参数也接受字符串,如slow,fast,就需要先将它们转化成数字,便于统一处理。下面是jQuery.speed函数的实现:
3968 speed: function(speed, easing, fn) {
3969 var opt = typeof speed === "object" ? speed : {
3970 complete: fn || !fn && easing ||
3971 jQuery.isFunction( speed ) && speed,
3972 duration: speed,
3973 easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
3974 };
3975
3976 opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
3977 jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
3978
3979 // Queueing
3980 opt.old = opt.complete;
3981 opt.complete = function(){
3982 if ( opt.queue !== false )
3983 jQuery(this).dequeue();
3984 if ( jQuery.isFunction( opt.old ) )
3985 opt.old.call( this );
3986 };
3987
3988 return opt;
3989 },
可以看到返回结果的duration属性是动态效果持续时间,easing属性是动态效果变化曲线(linear还是swing),complete属性是动态效果完成时的回调函数。对于3983行的代码,在jQuery中,多个连续的动画效果放在队列中,当最后一个动画效果完成之后就要调用dequeue方法将它删除。
回到animate函数,当设置optall.queue为fase时,对每个元素立即调用动态效果,each函数还记得是干什么的吧?否则,如果如果现在还有动态效果在执行,则暂时将当前动态效果放到队列中,等到前面的动态效果执行完毕后再执行,这正是queue函数的作用。举个例子:
queue和dequeue共同合作实现了动态效果的顺序调用。调用queue(callback)时,将callback放在到函数队列末尾,如果队列中只有一个函数,也就是刚才放入的函数,立即调用,否则不做任何事情,即等待。调用dequeue()时,如果队列为空,不做任何事情,否则将队列的头部的函数移去,再看是否队列中还有函数,有的话则调用队列首部的元素。用例子来说明,下面的代码要输入在Firebug控制台中:
第一次enqueue调用会立即执行函数,第二次、第三次调用则不会,每次调用dequeue弹出队列中的一个函数并执行下一个队列函数,最后一次dequeue调用只是队列中的最后一个函数弹出,但并不调用任何函数,这正是speed函数3983行代码做的事情。现在我们应该可以猜到,每个一个动画效果完成后会调用dequeue方法,这样便能够调用下一个动画效果。
回到animate方法,看3868行到3920行之间省略的代码:
3868 return this[ optall.queue === false ? "each" : "queue" ](function(){
3869
3870 var opt = jQuery.extend({}, optall), p,
3871 hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
3872 self = this;
3873
3874 for ( p in prop ) {
3875 if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
3876 return opt.complete.call(this);
3877
3878 if ( ( p == "height" || p == "width" ) && this.style ) {
3879 // Store display property
3880 opt.display = jQuery.css(this, "display");
3881
3882 // Make sure that nothing sneaks out
3883 opt.overflow = this.style.overflow;
3884 }
3885 }
3886
3887 if ( opt.overflow != null )
3888 this.style.overflow = "hidden";
3889
3890 opt.curAnim = jQuery.extend({}, prop);
3891
3892 jQuery.each( prop, function(name, val){
3893 var e = new jQuery.fx( self, opt, name );
3894
3895 if ( /toggle|show|hide/.test(val) )
3896 e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
3897 else {
3898 var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
3899 start = e.cur(true) || 0;
3900
3901 if ( parts ) {
3902 var end = parseFloat(parts[2]),
3903 unit = parts[3] || "px";
3904
3905 // We need to compute starting value
3906 if ( unit != "px" ) {
3907 self.style[ name ] = (end || 1) + unit;
3908 start = ((end || 1) / e.cur(true)) * start;
3909 self.style[ name ] = start + unit;
3910 }
3911
3912 // If a +=/-= token was provided, we're doing a relative animation
3913 if ( parts[1] )
3914 end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
3915
3916 e.custom( start, end, unit );
3917 } else
3918 e.custom( start, val, "" );
3919 }
3920 });
3921
3922 // For JS strict compliance
3923 return true;
代码看起来很复杂,但思想很简单,主要是对每个样式属性,计算其初始值和结束值,然后调用jQuery.fx对象的custom方法(第3916行和3918行),因此主要的动态效果实现的步骤在custom方法。计算初始值和结束值的复杂之处在于:(1)样式属性的值可以为show, hide, toggle,这需要特殊处理(3895-3896行),3896行会调用jQuery.fx对象的show,hide或toggle方法,它们最终调用的是custom方法,这里不再列出。(2)样式属性值可以通过+=或-=来指定相对值(3898行及3913-3914行)。(3) 样式属性值可以指定其它单位(3906-3010行)。接下来就看jQuery.fx对象的custom函数:
4037 custom: function(from, to, unit){
4038 this.startTime = now();
4039 this.start = from;
4040 this.end = to;
4041 this.unit = unit || this.unit || "px";
4042 this.now = this.start;
4043 this.pos = this.state = 0;
4044
4045 var self = this;
4046 function t(gotoEnd){
4047 return self.step(gotoEnd);
4048 }
4049
4050 t.elem = this.elem;
4051
4052 if ( t() && jQuery.timers.push(t) && !timerId ) {
4053 timerId = setInterval(function(){
4054 var timers = jQuery.timers;
4055
4056 for ( var i = 0; i < timers.length; i++ )
4057 if ( !timers[i]() )
4058 timers.splice(i--, 1);
4059
4060 if ( !timers.length ) {
4061 clearInterval( timerId );
4062 timerId = undefined;
4063 }
4064 }, 13);
4065 }
4066 },
先看4053-4064行,它设置了一个每隔13毫秒周期执行的函数,这个函数的做的事情就是更新动态效果的显示,可以看到jQuery采用的轮询方式来实现动态效果,我们在Ajax里已经看到过它了。第4038行记一动态效果的开始时间,这样每次更新时根据当前时间就知道已经进行了多少步了。第4046-4048中的t函数负责更新动态效果的显示,它又调用step方法。jQuery在所有负责更新动态效果的函数放在jQuery.timers对象中(4052行),4053-4045依次对它们进行调用,如果它们返回false,表示更新结束,需要将它从jQuery.timers中移除(第4057-4058行)。最后当所有更新动态效果的函数都已经完成,清除定时函数(第4060-4063行)。从上面知道,step函数负责具体的更新,它的实现如下:
4094 step: function(gotoEnd){
4095 var t = now();
4096
4097 if ( gotoEnd || t >= this.options.duration + this.startTime ) {
4098 this.now = this.end;
4099 this.pos = this.state = 1;
4100 this.update();
4101
4102 this.options.curAnim[ this.prop ] = true;
4103
4104 var done = true;
4105 for ( var i in this.options.curAnim )
4106 if ( this.options.curAnim[i] !== true )
4107 done = false;
4108
4109 if ( done ) {
4110 if ( this.options.display != null ) {
4111 // Reset the overflow
4112 this.elem.style.overflow = this.options.overflow;
4113
4114 // Reset the display
4115 this.elem.style.display = this.options.display;
4116 if ( jQuery.css(this.elem, "display") == "none" )
4117 this.elem.style.display = "block";
4118 }
4119
4120 // Hide the element if the "hide" operation was done
4121 if ( this.options.hide )
4122 jQuery(this.elem).hide();
4123
4124 // Reset the properties, if the item has been hidden or shown
4125 if ( this.options.hide || this.options.show )
4126 for ( var p in this.options.curAnim )
4127 jQuery.attr(this.elem.style, p, this.options.orig[p]);
4128
4129 // Execute the complete function
4130 this.options.complete.call( this.elem );
4131 }
4132
4133 return false;
4134 } else {
4135 var n = t - this.startTime;
4136 this.state = n / this.options.duration;
4137
4138 // Perform the easing function, defaults to swing
4139 this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
4140 this.now = this.start + ((this.end - this.start) * this.pos);
4141
4142 // Perform the next step of the animation
4143 this.update();
4144 }
4145
4146 return true;
4147 }
4148
4149 };
虽然代码很长,但并不复杂,第4097-4134处理可能更新完成的情况,第4134-4147行处理正在更新的情况。需要注意的是,一个动态效果可能同时涉及到多个样式属性的变化,只有当它们所有的属性的更新都完成,这个动态效果才能算完成,这正是4102-4107行的处理。如果更新完成(4109行),调用complete回调函数(4130行),并返回false,表示已经处理完(4133行),其它代码主要是恢复原来的一些属性。正在更新时,计算当前的处于状态(4136行),计算出该更新多少(4139-4140行),最后调用update函数更新元素的属性,update函数不再列出。
以上是animate函数的实现,至于show/hide直接调用animate函数同时更新width和height,slideDown和slideUp调用animate更新height,fadeIn和fadeOut调用animate更新opacity,也不再列出。
3865 animate: function( prop, speed, easing, callback ) {
3866 var optall = jQuery.speed(speed, easing, callback);
3867
3868 return this[ optall.queue === false ? "each" : "queue" ](function(){
// 中间省略好多行...
3920 });
animate接受四个参数,第一个参数是是需要动态改变的样式属性及其最终值,例如{width: "70%", fontSize:"3em"}表示宽度会逐渐缩小到原来的70%,而字体大小会逐渐变到3em,样式属性必须能接收数字值,例如不能动态改变backGround样式。第二个参数是动态效果的持续时间,第三个参数是动态效果是线性变化(linear,匀速变化)还是摆动变化(swing,像单摆那样运动,先慢后快)。第四个参数是动态效果停止时的回调函数。第3866行的后三个参数合并到一个对象中,方便以后获取。有人可能要问,直接从参数中获取不是更方便吗?两个原因,一是后三个参数都是可选的,如果最后一个参数是函数,它总是代表回调函数,当调用animate({wdith:150}, function() {...})时,speed参数会得到回调函数,但实际上它应该由callback参数访问,经过jQuery.speed函数正规化后,回调函数总由optall.complete访问。另外,为了方便用户操作,speed参数也接受字符串,如slow,fast,就需要先将它们转化成数字,便于统一处理。下面是jQuery.speed函数的实现:
3968 speed: function(speed, easing, fn) {
3969 var opt = typeof speed === "object" ? speed : {
3970 complete: fn || !fn && easing ||
3971 jQuery.isFunction( speed ) && speed,
3972 duration: speed,
3973 easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
3974 };
3975
3976 opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
3977 jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
3978
3979 // Queueing
3980 opt.old = opt.complete;
3981 opt.complete = function(){
3982 if ( opt.queue !== false )
3983 jQuery(this).dequeue();
3984 if ( jQuery.isFunction( opt.old ) )
3985 opt.old.call( this );
3986 };
3987
3988 return opt;
3989 },
可以看到返回结果的duration属性是动态效果持续时间,easing属性是动态效果变化曲线(linear还是swing),complete属性是动态效果完成时的回调函数。对于3983行的代码,在jQuery中,多个连续的动画效果放在队列中,当最后一个动画效果完成之后就要调用dequeue方法将它删除。
回到animate函数,当设置optall.queue为fase时,对每个元素立即调用动态效果,each函数还记得是干什么的吧?否则,如果如果现在还有动态效果在执行,则暂时将当前动态效果放到队列中,等到前面的动态效果执行完毕后再执行,这正是queue函数的作用。举个例子:
<font face="monospace"> $("#go1").<a href="http://docs.jquery.com/Events/click" title="Events/click">click</a>(function(){对第一个调用,由于queue设为false,两个动态效果同时调用(宽度变小同时字体增大),持续时间为1000毫秒。对第二个调用,两个动态效果按次序调用,先是宽度变小(字体不变),然后再是字体变大(宽度不变),持续时间为2000毫秒。
$("#block1").<strong class="selflink">animate</strong>( { width:"90%" }, { queue:false, duration:1000 } )
.<strong class="selflink">animate</strong>( { fontSize:"24px" }, 1000 )
});
$("#go2").<a href="http://docs.jquery.com/Events/click" title="Events/click">click</a>(function(){
$("#block2").<strong class="selflink">animate</strong>( { width:"90%"}, 1000 )
.<strong class="selflink">animate</strong>( { fontSize:"24px" } , 1000 )
});
</font>
queue和dequeue共同合作实现了动态效果的顺序调用。调用queue(callback)时,将callback放在到函数队列末尾,如果队列中只有一个函数,也就是刚才放入的函数,立即调用,否则不做任何事情,即等待。调用dequeue()时,如果队列为空,不做任何事情,否则将队列的头部的函数移去,再看是否队列中还有函数,有的话则调用队列首部的元素。用例子来说明,下面的代码要输入在Firebug控制台中:
/>>> $(document).queue(function() { console.log("func1");})
func1
/>>> $(document).queue(function() { console.log("func2");})
/>>> $(document).queue(function() { console.log("func3");})
/>>> $(document).dequeue()
func2
/>>> $(document).dequeue()
func3
/>>> $(document).dequeue()
第一次enqueue调用会立即执行函数,第二次、第三次调用则不会,每次调用dequeue弹出队列中的一个函数并执行下一个队列函数,最后一次dequeue调用只是队列中的最后一个函数弹出,但并不调用任何函数,这正是speed函数3983行代码做的事情。现在我们应该可以猜到,每个一个动画效果完成后会调用dequeue方法,这样便能够调用下一个动画效果。
回到animate方法,看3868行到3920行之间省略的代码:
3868 return this[ optall.queue === false ? "each" : "queue" ](function(){
3869
3870 var opt = jQuery.extend({}, optall), p,
3871 hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
3872 self = this;
3873
3874 for ( p in prop ) {
3875 if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
3876 return opt.complete.call(this);
3877
3878 if ( ( p == "height" || p == "width" ) && this.style ) {
3879 // Store display property
3880 opt.display = jQuery.css(this, "display");
3881
3882 // Make sure that nothing sneaks out
3883 opt.overflow = this.style.overflow;
3884 }
3885 }
3886
3887 if ( opt.overflow != null )
3888 this.style.overflow = "hidden";
3889
3890 opt.curAnim = jQuery.extend({}, prop);
3891
3892 jQuery.each( prop, function(name, val){
3893 var e = new jQuery.fx( self, opt, name );
3894
3895 if ( /toggle|show|hide/.test(val) )
3896 e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
3897 else {
3898 var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
3899 start = e.cur(true) || 0;
3900
3901 if ( parts ) {
3902 var end = parseFloat(parts[2]),
3903 unit = parts[3] || "px";
3904
3905 // We need to compute starting value
3906 if ( unit != "px" ) {
3907 self.style[ name ] = (end || 1) + unit;
3908 start = ((end || 1) / e.cur(true)) * start;
3909 self.style[ name ] = start + unit;
3910 }
3911
3912 // If a +=/-= token was provided, we're doing a relative animation
3913 if ( parts[1] )
3914 end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
3915
3916 e.custom( start, end, unit );
3917 } else
3918 e.custom( start, val, "" );
3919 }
3920 });
3921
3922 // For JS strict compliance
3923 return true;
代码看起来很复杂,但思想很简单,主要是对每个样式属性,计算其初始值和结束值,然后调用jQuery.fx对象的custom方法(第3916行和3918行),因此主要的动态效果实现的步骤在custom方法。计算初始值和结束值的复杂之处在于:(1)样式属性的值可以为show, hide, toggle,这需要特殊处理(3895-3896行),3896行会调用jQuery.fx对象的show,hide或toggle方法,它们最终调用的是custom方法,这里不再列出。(2)样式属性值可以通过+=或-=来指定相对值(3898行及3913-3914行)。(3) 样式属性值可以指定其它单位(3906-3010行)。接下来就看jQuery.fx对象的custom函数:
4037 custom: function(from, to, unit){
4038 this.startTime = now();
4039 this.start = from;
4040 this.end = to;
4041 this.unit = unit || this.unit || "px";
4042 this.now = this.start;
4043 this.pos = this.state = 0;
4044
4045 var self = this;
4046 function t(gotoEnd){
4047 return self.step(gotoEnd);
4048 }
4049
4050 t.elem = this.elem;
4051
4052 if ( t() && jQuery.timers.push(t) && !timerId ) {
4053 timerId = setInterval(function(){
4054 var timers = jQuery.timers;
4055
4056 for ( var i = 0; i < timers.length; i++ )
4057 if ( !timers[i]() )
4058 timers.splice(i--, 1);
4059
4060 if ( !timers.length ) {
4061 clearInterval( timerId );
4062 timerId = undefined;
4063 }
4064 }, 13);
4065 }
4066 },
先看4053-4064行,它设置了一个每隔13毫秒周期执行的函数,这个函数的做的事情就是更新动态效果的显示,可以看到jQuery采用的轮询方式来实现动态效果,我们在Ajax里已经看到过它了。第4038行记一动态效果的开始时间,这样每次更新时根据当前时间就知道已经进行了多少步了。第4046-4048中的t函数负责更新动态效果的显示,它又调用step方法。jQuery在所有负责更新动态效果的函数放在jQuery.timers对象中(4052行),4053-4045依次对它们进行调用,如果它们返回false,表示更新结束,需要将它从jQuery.timers中移除(第4057-4058行)。最后当所有更新动态效果的函数都已经完成,清除定时函数(第4060-4063行)。从上面知道,step函数负责具体的更新,它的实现如下:
4094 step: function(gotoEnd){
4095 var t = now();
4096
4097 if ( gotoEnd || t >= this.options.duration + this.startTime ) {
4098 this.now = this.end;
4099 this.pos = this.state = 1;
4100 this.update();
4101
4102 this.options.curAnim[ this.prop ] = true;
4103
4104 var done = true;
4105 for ( var i in this.options.curAnim )
4106 if ( this.options.curAnim[i] !== true )
4107 done = false;
4108
4109 if ( done ) {
4110 if ( this.options.display != null ) {
4111 // Reset the overflow
4112 this.elem.style.overflow = this.options.overflow;
4113
4114 // Reset the display
4115 this.elem.style.display = this.options.display;
4116 if ( jQuery.css(this.elem, "display") == "none" )
4117 this.elem.style.display = "block";
4118 }
4119
4120 // Hide the element if the "hide" operation was done
4121 if ( this.options.hide )
4122 jQuery(this.elem).hide();
4123
4124 // Reset the properties, if the item has been hidden or shown
4125 if ( this.options.hide || this.options.show )
4126 for ( var p in this.options.curAnim )
4127 jQuery.attr(this.elem.style, p, this.options.orig[p]);
4128
4129 // Execute the complete function
4130 this.options.complete.call( this.elem );
4131 }
4132
4133 return false;
4134 } else {
4135 var n = t - this.startTime;
4136 this.state = n / this.options.duration;
4137
4138 // Perform the easing function, defaults to swing
4139 this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
4140 this.now = this.start + ((this.end - this.start) * this.pos);
4141
4142 // Perform the next step of the animation
4143 this.update();
4144 }
4145
4146 return true;
4147 }
4148
4149 };
虽然代码很长,但并不复杂,第4097-4134处理可能更新完成的情况,第4134-4147行处理正在更新的情况。需要注意的是,一个动态效果可能同时涉及到多个样式属性的变化,只有当它们所有的属性的更新都完成,这个动态效果才能算完成,这正是4102-4107行的处理。如果更新完成(4109行),调用complete回调函数(4130行),并返回false,表示已经处理完(4133行),其它代码主要是恢复原来的一些属性。正在更新时,计算当前的处于状态(4136行),计算出该更新多少(4139-4140行),最后调用update函数更新元素的属性,update函数不再列出。
以上是animate函数的实现,至于show/hide直接调用animate函数同时更新width和height,slideDown和slideUp调用animate更新height,fadeIn和fadeOut调用animate更新opacity,也不再列出。