jQuery1.9.1源码分析--Animation模块

  1 var fxNow,
  2         // 使用一个ID来执行动画setInterval 
  3         timerId,
  4         rfxtypes = /^(?:toggle|show|hide)$/,
  5         // eg: +=30.5px
  6         // 执行exec匹配["+=30.5px", "+", "30.5", "px"]
  7         rfxnum = new RegExp('^(?:([+-])=|)(' + core_pnum + ')([a-z%]*)$', 'i'),
  8         // 以“queueHooks”结尾
  9         rrun = /queueHooks$/,
 10         animationPrefilters = [defaultPrefilter],
 11         tweeners = {
 12             // 在动画前再次对动画参数做调整
 13             '*': [
 14                 function(prop, value) {
 15                     var end, unit,
 16                         // this指向animation对象
 17                         // 返回一个Tween构造函数实例
 18                         tween = this.createTween(prop, value),
 19                         // eg:["+=30.5px", "+", "30.5", "px"]
 20                         parts = rfxnum.exec(value),
 21                         // 计算当前属性样式值
 22                         target = tween.cur(),
 23                         start = +target || 0,
 24                         scale = 1,
 25                         maxIterations = 20;
 26 
 27                     if (parts) {
 28                         // 数值
 29                         end = +parts[2];
 30                         // 单位
 31                         // jQuery.cssNumber里面的值是不需要单位的
 32                         unit = parts[3] || (jQuery.cssNumber[prop] ? '' : 'px');
 33 
 34                         // We need to compute starting value
 35                         // 我们需要计算开始值
 36                         if (unit !== 'px' && start) {
 37                             // Iteratively approximate from a nonzero starting point
 38                             // Prefer the current property, because this process will be trivial if it uses the same units
 39                             // Fallback to end or a simple constant
 40                             // 尝试从元素样式中获取开始值
 41                             start = jQuery.css(tween.elem, prop, true) || end || 1;
 42 
 43                             do {
 44                                 // If previos iteration zeroed out, double until we get *something*
 45                                 // Use a string for doubling factor so we don't accidentally see scale as unchanged below
 46                                 scale = scale || '.5';
 47 
 48                                 // Adjust and apply
 49                                 start = start / scale;
 50                                 jQuery.style(tween.elem, prop, start + unit);
 51 
 52                                 // Update scale, tolerating zero or NaN from tween.cur()
 53                                 // And breaking the loop if scale is unchanged or perfect. or if we've just had enough
 54                             } while (scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations);
 55                         }
 56 
 57                         tween.unit = unit;
 58                         tween.start = start;
 59                         // If a +=/-= token was provided, we're doing a relative animation
 60                         tween.end = parts[1] ? start + (parts[1] + 1) * end : end;
 61                     }
 62                     return tween;
 63                 }
 64             ]
 65         };
 66 
 67     // Animations created synchronous will run synchronously
 68     // TODO
 69     // 返回一个时间戳,然后用setTimeout延时将fxNow设置为undefined
 70 
 71     function createFxNow() {
 72         setTimeout(function() {
 73             fxNow = undefined;
 74         });
 75         return (fxNow = jQuery.now());
 76     }
 77 
 78     function createTweens(animation, props) {
 79         // 遍历props动画属性对象,并执行回调
 80         jQuery.each(props, function(prop, value) {
 81             // 如果tweeners[prop]数组存在,将它和tweeners['*']连接
 82             var collection = (tweeners[prop] || []).concat(tweeners['*']),
 83                 index = 0,
 84                 length = collection.length;
 85 
 86             // 遍历函数数组
 87             for (; index < length; index++) {
 88                 // 如果该函数有返回值,且==true,退出函数
 89                 if (collection[index].call(animation, prop, value)) {
 90                     // We're done with this property
 91                     return;
 92                 }
 93             }
 94         });
 95     }
 96 
 97     function Animation(elem, properties, options) {
 98         var result, stopped, index = 0,
 99             length = animationPrefilters.length,
100             // deferred无论成功还是失败都会删除elem元素
101             deferred = jQuery.Deferred().always(function() {
102                 // don't match elem in the :animated selector
103                 // 在“:animated”选择器中不会匹配到它们
104                 delete tick.elem;
105             }),
106             tick = function() {
107                 if (stopped) {
108                     return false;
109                 }
110                 var // 计算当前动画时间戳
111                     currentTime = fxNow || createFxNow(),
112                     // 结束时间减当前时间,计算出剩余时间
113                     remaining = Math.max(0, animation.startTime + animation.duration - currentTime),
114                     // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
115                     // 剩余时间百分比
116                     temp = remaining / animation.duration || 0,
117                     // 已执行百分比
118                     percent = 1 - temp,
119                     index = 0,
120                     // 动画属性对应的tweens
121                     length = animation.tweens.length;
122 
123                 // 遍历tweens,并执行对应的run方法,将已执行百分比通过传参传入
124                 // run方法通过缓动算法计算出样式值,然后应用到元素上
125                 for (; index < length; index++) {
126                     animation.tweens[index].run(percent);
127                 }
128 
129                 // 触发notify回调列表
130                 deferred.notifyWith(elem, [animation, percent, remaining]);
131 
132                 // 如果执行进度为完成且tweens数组有元素
133                 // 返回剩余时间
134                 if (percent < 1 && length) {
135                     return remaining;
136                 } else {
137                     // 否则表示已完成,触发resolve回调列表,
138                     // 并返回false值
139                     deferred.resolveWith(elem, [animation]);
140                     return false;
141                 }
142             },
143             animation = deferred.promise({
144                 // 动画元素
145                 elem: elem,
146                 // 需要动画的属性
147                 props: jQuery.extend({}, properties),
148                 // 给optall添加specialEasing属性对象
149                 opts: jQuery.extend(true, {
150                     specialEasing: {}
151                 }, options),
152                 // 原始动画属性
153                 originalProperties: properties,
154                 // 原始的配置项optall
155                 originalOptions: options,
156                 // 动画开始时间,使用当前时间的毫秒数
157                 startTime: fxNow || createFxNow(),
158                 // 动画时长
159                 duration: options.duration,
160                 tweens: [],
161                 createTween: function(prop, end) {
162                     var tween = jQuery.Tween(elem, animation.opts, prop, end, animation.opts.specialEasing[prop] || animation.opts.easing);
163                     animation.tweens.push(tween);
164                     return tween;
165                 },
166                 stop: function(gotoEnd) {
167                     var index = 0,
168                         // if we are going to the end, we want to run all the tweens
169                         // otherwise we skip this part
170                         length = gotoEnd ? animation.tweens.length : 0;
171                     if (stopped) {
172                         return this;
173                     }
174                     stopped = true;
175                     for (; index < length; index++) {
176                         animation.tweens[index].run(1);
177                     }
178 
179                     // resolve when we played the last frame
180                     // otherwise, reject
181                     if (gotoEnd) {
182                         deferred.resolveWith(elem, [animation, gotoEnd]);
183                     } else {
184                         deferred.rejectWith(elem, [animation, gotoEnd]);
185                     }
186                     return this;
187                 }
188             }),
189             props = animation.props;
190 
191         /*
192         将是动画属性转换成驼峰式,并设置其相应的缓动属性,
193         如果存在cssHooks钩子对象,则需要另作一番处理
194          */
195         propFilter(props, animation.opts.specialEasing);
196 
197         // 遍历动画预过滤器,并执行回调
198         // 其中defaultPrefilter为默认预过滤器,每次都会执行
199         for (; index < length; index++) {
200             result = animationPrefilters[index].call(animation, elem, props, animation.opts);
201             // 如果有返回值,退出函数
202             if (result) {
203                 return result;
204             }
205         }
206 
207         createTweens(animation, props);
208 
209         if (jQuery.isFunction(animation.opts.start)) {
210             animation.opts.start.call(elem, animation);
211         }
212 
213         // 开始执行动画
214         jQuery.fx.timer(
215             jQuery.extend(tick, {
216                 elem: elem,
217                 anim: animation,
218                 queue: animation.opts.queue
219             }));
220 
221         // attach callbacks from options
222         return animation.progress(animation.opts.progress).done(animation.opts.done, animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always);
223     }
224 
225     /**
226      * 动画属性调整与过滤
227      * 
228      * 将是动画属性转换成驼峰式,并设置其相应的缓动属性,
229      * 如果存在cssHooks钩子对象,则需要另作一番处理
230      * @param  {[type]} props         [需要动画的属性]
231      * @param  {[type]} specialEasing [description]
232      * @return {[type]}               [description]
233      */
234     function propFilter(props, specialEasing) {
235         var value, name, index, easing, hooks;
236 
237         // camelCase, specialEasing and expand cssHook pass
238         for (index in props) {
239             // 驼峰化属性
240             name = jQuery.camelCase(index);
241             // TODO
242             easing = specialEasing[name];
243             // 属性值
244             value = props[index];
245             // 如果属性值是数组
246             if (jQuery.isArray(value)) {
247                 easing = value[1];
248                 // 取数组第一个元素为属性值
249                 value = props[index] = value[0];
250             }
251 
252             // 如果属性名精过驼峰化后,删除原有的属性名,减少占用内存
253             if (index !== name) {
254                 props[name] = value;
255                 delete props[index];
256             }
257 
258             // 处理兼容性的钩子对象
259             hooks = jQuery.cssHooks[name];
260             // 如果存在钩子对象且有expand属性
261             if (hooks && "expand" in hooks) {
262                 // 返回expand处理后的value值
263                 // 该类型是一个对象,属性是
264                 // (margin|padding|borderWidth)(Top|Right|Bottom|Left)
265                 value = hooks.expand(value);
266 
267                 // 我们已经不需要name属性了
268                 delete props[name];
269 
270                 // not quite $.extend, this wont overwrite keys already present.
271                 // also - reusing 'index' from above because we have the correct "name"
272                 for (index in value) {
273                     // 如果props没有(margin|padding|borderWidth)(Top|Right|Bottom|Left)属性
274                     // 添加该属性和对应的值,并设置缓动属性
275                     if (!(index in props)) {
276                         props[index] = value[index];
277                         specialEasing[index] = easing;
278                     }
279                 }
280             } else {
281                 // 没有钩子对象就直接设置其为缓动属性
282                 specialEasing[name] = easing;
283             }
284         }
285     }
286 
287     jQuery.Animation = jQuery.extend(Animation, {
288 
289         tweener: function(props, callback) {
290             if (jQuery.isFunction(props)) {
291                 callback = props;
292                 props = ["*"];
293             } else {
294                 props = props.split(" ");
295             }
296 
297             var prop, index = 0,
298                 length = props.length;
299 
300             for (; index < length; index++) {
301                 prop = props[index];
302                 tweeners[prop] = tweeners[prop] || [];
303                 tweeners[prop].unshift(callback);
304             }
305         },
306         // 为animationPrefilters回调数组添加回调
307         prefilter: function(callback, prepend) {
308             if (prepend) {
309                 animationPrefilters.unshift(callback);
310             } else {
311                 animationPrefilters.push(callback);
312             }
313         }
314     });
315 
316     /**
317      * 动画预处理
318      * 添加fx队列缓存(没有的话),对动画属性“width/height,overflow”, 值有“toggle/show/hide”采取的一些措施
319      * 
320      * @param  {[type]} elem  [动画元素]
321      * @param  {[type]} props [动画属性]
322      * @param  {[type]} opts  [动画配置项]
323      * @return {[type]}       [description]
324      */
325     function defaultPrefilter(elem, props, opts) { /*jshint validthis:true */
326         var prop, index, length, value, dataShow, toggle, tween, hooks, oldfire,
327             // animation对象(同时是个deferred对象)
328             anim = this,
329             style = elem.style,
330             orig = {},
331             handled = [],
332             hidden = elem.nodeType && isHidden(elem);
333 
334         // handle queue: false promises
335         if (!opts.queue) {
336             // 获取或者设置动画队列钩子
337             hooks = jQuery._queueHooks(elem, "fx");
338             // 如果hooks.unqueued为null/undefined
339             if (hooks.unqueued == null) {
340                 hooks.unqueued = 0;
341                 // 获取旧的empty回调对象
342                 // 用于清除动画队列缓存
343                 oldfire = hooks.empty.fire;
344                 // 装饰,添加新的职责
345                 hooks.empty.fire = function() {
346                     // 当hooks.unqueued为0时执行清除动画队列缓存
347                     if (!hooks.unqueued) {
348                         oldfire();
349                     }
350                 };
351             }
352             hooks.unqueued++;
353 
354             anim.always(function() {
355                 // doing this makes sure that the complete handler will be called
356                 // before this completes
357                 // 延迟处理,确保该回调完成才调用下面回调
358                 anim.always(function() {
359                     hooks.unqueued--;
360                     // 如果动画队列没有元素了,清空缓存
361                     if (!jQuery.queue(elem, "fx").length) {
362                         hooks.empty.fire();
363                     }
364                 });
365             });
366         }
367 
368         // height/width overflow pass
369         // 对width或height的DOM元素的动画前的处理
370         if (elem.nodeType === 1 && ("height" in props || "width" in props)) {
371             // Make sure that nothing sneaks out
372             // Record all 3 overflow attributes because IE does not
373             // change the overflow attribute when overflowX and
374             // overflowY are set to the same value
375             // IE不会改变overflow属性当iverflowX和overflowY的值相同时。
376             // 因此我们要记录三个overflow的属性
377             opts.overflow = [style.overflow, style.overflowX, style.overflowY];
378 
379             // Set display property to inline-block for height/width
380             // animations on inline elements that are having width/height animated
381             // 将inline元素(非浮动的)设置为inline-block或者BFC(iE6/7),使它们的width和height可改变
382             if (jQuery.css(elem, "display") === "inline" && jQuery.css(elem, "float") === "none") {
383 
384                 // inline-level elements accept inline-block;
385                 // block-level elements need to be inline with layout
386                 if (!jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay(elem.nodeName) === "inline") {
387                     style.display = "inline-block";
388 
389                 } else {
390                     style.zoom = 1;
391                 }
392             }
393         }
394 
395         if (opts.overflow) {
396             style.overflow = "hidden";
397             // 如果不支持父元素随着子元素宽度改变而改变
398             // 动画结束后将style设置为初始状态
399             if (!jQuery.support.shrinkWrapBlocks) {
400                 anim.always(function() {
401                     style.overflow = opts.overflow[0];
402                     style.overflowX = opts.overflow[1];
403                     style.overflowY = opts.overflow[2];
404                 });
405             }
406         }
407 
408 
409         // show/hide pass
410         // 遍历动画属性
411         for (index in props) {
412             // 获取目标值
413             value = props[index];
414             // 判断值是否有toggle|show|hide
415             if (rfxtypes.exec(value)) {
416                 delete props[index];
417                 // 是否需要toggle
418                 toggle = toggle || value === "toggle";
419                 // 如果hide(或者show)状态的初始值和我们动画的值相同,就不需要做处理
420                 if (value === (hidden ? "hide" : "show")) {
421                     continue;
422                 }
423                 // 将需要show/hide/toggle的属性保存到handled数组中
424                 handled.push(index);
425             }
426         }
427 
428         length = handled.length;
429         // 如果handled数组有元素
430         // 对需要toggle|show|hide的属性处理
431         if (length) {
432             // 获取或者设置元素的fxshow缓存(保存显示状态)
433             dataShow = jQuery._data(elem, "fxshow") || jQuery._data(elem, "fxshow", {});
434             // 如果元素已经有hidden属性,说明我们设置过了,
435             // 取该值
436             if ("hidden" in dataShow) {
437                 hidden = dataShow.hidden;
438             }
439 
440             // store state if its toggle - enables .stop().toggle() to "reverse"
441             // 如果需要toggle,将hidden状态取反
442             if (toggle) {
443                 dataShow.hidden = !hidden;
444             }
445             // 如果元素隐藏了就显示出来,为了后期的动画
446             if (hidden) {
447                 jQuery(elem).show();
448             } else {
449                 // 否则动画结束后才隐藏
450                 anim.done(function() {
451                     jQuery(elem).hide();
452                 });
453             }
454             // 动画结束后删除fxshow缓存,并恢复元素原始样式
455             anim.done(function() {
456                 var prop;
457                 jQuery._removeData(elem, "fxshow");
458                 for (prop in orig) {
459                     jQuery.style(elem, prop, orig[prop]);
460                 }
461             });
462             for (index = 0; index < length; index++) {
463                 prop = handled[index];
464                 // 创建Tween实例
465                 tween = anim.createTween(prop, hidden ? dataShow[prop] : 0);
466                 // 获取元素原始样式值
467                 orig[prop] = dataShow[prop] || jQuery.style(elem, prop);
468 
469                 // 如果dataShow引用的缓存没有show|hide|toggle属性
470                 if (!(prop in dataShow)) {
471                     // 添加该属性,并赋初值
472                     dataShow[prop] = tween.start;
473                     if (hidden) {
474                         tween.end = tween.start;
475                         tween.start = prop === "width" || prop === "height" ? 1 : 0;
476                     }
477                 }
478             }
479         }
480     }
481 
482     // 实例化init构造函数
483     // 对单个动画属性,在初始化的时候计算开始值
484     function Tween(elem, options, prop, end, easing) {
485         return new Tween.prototype.init(elem, options, prop, end, easing);
486     }
487     jQuery.Tween = Tween;
488 
489     Tween.prototype = {
490         constructor: Tween,
491         init: function(elem, options, prop, end, easing, unit) {
492             this.elem = elem;
493             this.prop = prop;
494             this.easing = easing || "swing";
495             this.options = options;
496             this.start = this.now = this.cur();
497             this.end = end;
498             this.unit = unit || (jQuery.cssNumber[prop] ? "" : "px");
499         },
500         cur: function() {
501             var hooks = Tween.propHooks[this.prop];
502 
503             return hooks && hooks.get ? hooks.get(this) : Tween.propHooks._default.get(this);
504         },
505         // 通过缓动算法计算出样式值,然后应用到元素上
506         run: function(percent) {
507             var eased, hooks = Tween.propHooks[this.prop];
508 
509             // 当前执行位置,
510             // 如果有时长,就用缓动算法
511             if (this.options.duration) {
512                 this.pos = eased = jQuery.easing[this.easing](
513                     percent, this.options.duration * percent, 0, 1, this.options.duration);
514             } else {
515                 this.pos = eased = percent;
516             }
517             // 当前时间戳
518             this.now = (this.end - this.start) * eased + this.start;
519 
520             if (this.options.step) {
521                 this.options.step.call(this.elem, this.now, this);
522             }
523 
524             // 有钩子对象就执行set方法,否则使用默认set方法
525             if (hooks && hooks.set) {
526                 hooks.set(this);
527             } else {
528                 Tween.propHooks._default.set(this);
529             }
530             return this;
531         }
532     };
533 
534     Tween.prototype.init.prototype = Tween.prototype;
535 
536     Tween.propHooks = {
537         _default: {
538             // 默认的获取样式初始值方法
539             get: function(tween) {
540                 var result;
541 
542                 if (tween.elem[tween.prop] != null && (!tween.elem.style || tween.elem.style[tween.prop] == null)) {
543                     return tween.elem[tween.prop];
544                 }
545 
546                 // passing an empty string as a 3rd parameter to .css will automatically
547                 // attempt a parseFloat and fallback to a string if the parse fails
548                 // so, simple values such as "10px" are parsed to Float.
549                 // complex values such as "rotate(1rad)" are returned as is.
550                 result = jQuery.css(tween.elem, tween.prop, "");
551                 // Empty strings, null, undefined and "auto" are converted to 0.
552                 return !result || result === "auto" ? 0 : result;
553             },
554             // 设置元素样式
555             set: function(tween) {
556                 // use step hook for back compat - use cssHook if its there - use .style if its
557                 // available and use plain properties where available
558                 if (jQuery.fx.step[tween.prop]) {
559                     jQuery.fx.step[tween.prop](tween);
560                 } else if (tween.elem.style && (tween.elem.style[jQuery.cssProps[tween.prop]] != null || jQuery.cssHooks[tween.prop])) {
561                     jQuery.style(tween.elem, tween.prop, tween.now + tween.unit);
562                 } else {
563                     tween.elem[tween.prop] = tween.now;
564                 }
565             }
566         }
567     };
568 
569     // Remove in 2.0 - this supports IE8's panic based approach
570     // to setting things on disconnected nodes
571     Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
572         set: function(tween) {
573             if (tween.elem.nodeType && tween.elem.parentNode) {
574                 tween.elem[tween.prop] = tween.now;
575             }
576         }
577     };
578 
579     jQuery.each(["toggle", "show", "hide"], function(i, name) {
580         var cssFn = jQuery.fn[name];
581         jQuery.fn[name] = function(speed, easing, callback) {
582             return speed == null || typeof speed === "boolean" ? cssFn.apply(this, arguments) : this.animate(genFx(name, true), speed, easing, callback);
583         };
584     });
585 
586     jQuery.fn.extend({
587         fadeTo: function(speed, to, easing, callback) {
588 
589             // show any hidden elements after setting opacity to 0
590             return this.filter(isHidden).css("opacity", 0).show()
591 
592             // animate to the value specified
593             .end().animate({
594                 opacity: to
595             }, speed, easing, callback);
596         },
597         animate: function(prop, speed, easing, callback) {
598             var // prop对象是否为空
599                 empty = jQuery.isEmptyObject(prop),
600                 // 返回{complete, duration, easing, queue, old}
601                 optall = jQuery.speed(speed, easing, callback),
602                 // TODO
603                 doAnimation = function() {
604                     // Operate on a copy of prop so per-property easing won't be lost
605                     var anim = Animation(this, jQuery.extend({}, prop), optall);
606                     doAnimation.finish = function() {
607                         anim.stop(true);
608                     };
609                     // Empty animations, or finishing resolves immediately
610                     if (empty || jQuery._data(this, "finish")) {
611                         anim.stop(true);
612                     }
613                 };
614             doAnimation.finish = doAnimation;
615 
616                 // 如果prop为空对象或者queue为false(即不进行动画队列),
617                 // 遍历元素集并执行doAnimation回调
618             return empty || optall.queue === false ? this.each(doAnimation) :
619                 // 否则prop不为空且需要队列执行,
620                 // 将doAnimation添加到该元素的队列中
621                 // jQuery.queue('fx', doAnimation)
622                 this.queue(optall.queue, doAnimation);
623         },
624         // 停止所有在指定元素上正在运行的动画。
625         stop: function(type, clearQueue, gotoEnd) {
626             var stopQueue = function(hooks) {
627                 var stop = hooks.stop;
628                 delete hooks.stop;
629                 stop(gotoEnd);
630             };
631 
632             if (typeof type !== "string") {
633                 gotoEnd = clearQueue;
634                 clearQueue = type;
635                 type = undefined;
636             }
637             if (clearQueue && type !== false) {
638                 this.queue(type || "fx", []);
639             }
640 
641             return this.each(function() {
642                 var dequeue = true,
643                     index = type != null && type + "queueHooks",
644                     timers = jQuery.timers,
645                     data = jQuery._data(this);
646 
647                 if (index) {
648                     if (data[index] && data[index].stop) {
649                         stopQueue(data[index]);
650                     }
651                 } else {
652                     for (index in data) {
653                         if (data[index] && data[index].stop && rrun.test(index)) {
654                             stopQueue(data[index]);
655                         }
656                     }
657                 }
658 
659                 for (index = timers.length; index--;) {
660                     if (timers[index].elem === this && (type == null || timers[index].queue === type)) {
661                         timers[index].anim.stop(gotoEnd);
662                         dequeue = false;
663                         timers.splice(index, 1);
664                     }
665                 }
666 
667                 // start the next in the queue if the last step wasn't forced
668                 // timers currently will call their complete callbacks, which will dequeue
669                 // but only if they were gotoEnd
670                 if (dequeue || !gotoEnd) {
671                     jQuery.dequeue(this, type);
672                 }
673             });
674         },
675         finish: function(type) {
676             if (type !== false) {
677                 type = type || "fx";
678             }
679             return this.each(function() {
680                 var index, data = jQuery._data(this),
681                     queue = data[type + "queue"],
682                     hooks = data[type + "queueHooks"],
683                     timers = jQuery.timers,
684                     length = queue ? queue.length : 0;
685 
686                 // enable finishing flag on private data
687                 data.finish = true;
688 
689                 // empty the queue first
690                 jQuery.queue(this, type, []);
691 
692                 if (hooks && hooks.cur && hooks.cur.finish) {
693                     hooks.cur.finish.call(this);
694                 }
695 
696                 // look for any active animations, and finish them
697                 for (index = timers.length; index--;) {
698                     if (timers[index].elem === this && timers[index].queue === type) {
699                         timers[index].anim.stop(true);
700                         timers.splice(index, 1);
701                     }
702                 }
703 
704                 // look for any animations in the old queue and finish them
705                 for (index = 0; index < length; index++) {
706                     if (queue[index] && queue[index].finish) {
707                         queue[index].finish.call(this);
708                     }
709                 }
710 
711                 // turn off finishing flag
712                 delete data.finish;
713             });
714         }
715     });
716 
717     // Generate parameters to create a standard animation
718     /**
719      * 用于填充slideDown/slideUp/slideToggle动画参数
720      * @param  {[String]} type         [show/hide/toggle]
721      * @param  {[type]} includeWidth [是否需要包含宽度]
722      * @return {[type]}              [description]
723      */
724     function genFx(type, includeWidth) {
725         var which,
726             attrs = {
727                 height: type
728             },
729             i = 0;
730 
731         // if we include width, step value is 1 to do all cssExpand values,
732         // if we don't include width, step value is 2 to skip over Left and Right
733         includeWidth = includeWidth ? 1 : 0;
734         // 不包含宽度,which就取“Top/Bottom”,
735         // 否则“Left/Right”
736         for (; i < 4; i += 2 - includeWidth) {
737             which = cssExpand[i];
738             attrs["margin" + which] = attrs["padding" + which] = type;
739         }
740 
741         if (includeWidth) {
742             attrs.opacity = attrs.width = type;
743         }
744 
745         return attrs;
746     }
747 
748     // Generate shortcuts for custom animations
749     jQuery.each({
750         slideDown: genFx("show"),
751         slideUp: genFx("hide"),
752         slideToggle: genFx("toggle"),
753         fadeIn: {
754             opacity: "show"
755         },
756         fadeOut: {
757             opacity: "hide"
758         },
759         fadeToggle: {
760             opacity: "toggle"
761         }
762     }, function(name, props) {
763         jQuery.fn[name] = function(speed, easing, callback) {
764             return this.animate(props, speed, easing, callback);
765         };
766     });
767 
768     /**
769      * 配置动画参数
770      * 
771      * 配置动画时长,动画结束回调(经装饰了),缓动算法,queue属性用来标识是动画队列
772      * @param  {[Number|Objecct]}   speed  [动画时长]
773      * @param  {[Function]}   easing [缓动算法]
774      * @param  {Function} fn     [动画结束会掉]
775      * @return {[Object]}          [description]
776      */
777     jQuery.speed = function(speed, easing, fn) {
778         var opt =
779             // speed是否为对象
780             speed && typeof speed === "object" ?
781             // 如果是,克隆speed对象
782             jQuery.extend({}, speed) :
783             // 否则返回一个新的对象
784             {
785                 // complete是我们的animate的回调方法,
786                 // 即动画结束时的回调
787                 // (speed, easing, fn)
788                 // (speed || easing, fn)
789                 // (fn)
790                 complete: fn || !fn && easing || jQuery.isFunction(speed) && speed,
791                 // 动画时长
792                 duration: speed,
793                 // 缓动
794                 easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
795             };
796 
797         opt.duration =
798             // jQuery.fx.off是否为真,如果是则将opt.duration设置为0,
799             // 这将会停止所有动画
800             jQuery.fx.off ? 0 :
801             // 否则判断duration属性值是否为数字类型,是则使用 
802             typeof opt.duration === "number" ? opt.duration :
803             // 否则判断duration属性值字符串是否在jQuery.fx.speeds(jQuery的预配置动画时长)属性key字段中,是则使用 
804             opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] :
805             // 否则就是用默认动画时长
806             jQuery.fx.speeds._default;
807 
808         // normalize opt.queue - true/undefined/null -> "fx"
809         // 如果opt.queue的值是true/undefined/null之一,
810         // 将其值设置为"fx"字符串,标示动画队列
811         if (opt.queue == null || opt.queue === true) {
812             opt.queue = "fx";
813         }
814 
815         // Queueing
816         // 将旧的回调(即我们添加的回调)存入opt.old
817         opt.old = opt.complete;
818 
819         // 给opt.complete重新定义,
820         // 在旧方法中通过装饰包装
821         opt.complete = function() {
822             if (jQuery.isFunction(opt.old)) {
823                 // 执行我们的回调
824                 opt.old.call(this);
825             }
826 
827             // 如果有队列,执行我们下一个队列
828             if (opt.queue) {
829                 jQuery.dequeue(this, opt.queue);
830             }
831         };
832 
833         // 返回opt
834         /*
835         {complete, duration, easing, queue, old}
836          */
837         return opt;
838     };
839 
840     jQuery.easing = {
841         linear: function(p) {
842             return p;
843         },
844         swing: function(p) {
845             return 0.5 - Math.cos(p * Math.PI) / 2;
846         }
847     };
848 
849     // 全局timers数组,保存着所有动画tick
850     jQuery.timers = [];
851     jQuery.fx = Tween.prototype.init;
852     // setInterval回调
853     jQuery.fx.tick = function() {
854         var timer, timers = jQuery.timers,
855             i = 0;
856 
857         fxNow = jQuery.now();
858 
859         // 遍历所有tick
860         for (; i < timers.length; i++) {
861             timer = timers[i];
862             // Checks the timer has not already been removed
863             // 如果当前tick返回的为假(经弱转换)
864             // 移除该tick
865             // 然后继续遍历当前项,因为数组长度被改变了
866             if (!timer() && timers[i] === timer) {
867                 timers.splice(i--, 1);
868             }
869         }
870 
871         // 如果没有tick回调了,停止定时器
872         if (!timers.length) {
873             jQuery.fx.stop();
874         }
875         fxNow = undefined;
876     };
877 
878     /**
879      * 
880      * 
881      * @param  {Object} timer tick回调
882      */
883     jQuery.fx.timer = function(timer) {
884         if (timer() && jQuery.timers.push(timer)) {
885             jQuery.fx.start();
886         }
887     };
888 
889     jQuery.fx.interval = 13;
890 
891     // 动画正式开始
892     jQuery.fx.start = function() {
893         if (!timerId) {
894             // 间隔执行jQuery.fx.tick
895             timerId = setInterval(jQuery.fx.tick, jQuery.fx.interval);
896         }
897     };
898 
899     jQuery.fx.stop = function() {
900         clearInterval(timerId);
901         timerId = null;
902     };
903 
904     jQuery.fx.speeds = {
905         slow: 600,
906         fast: 200,
907         // Default speed
908         _default: 400
909     };
910 
911     // Back Compat <1.8 extension point
912     jQuery.fx.step = {};
913 
914     if (jQuery.expr && jQuery.expr.filters) {
915         jQuery.expr.filters.animated = function(elem) {
916             return jQuery.grep(jQuery.timers, function(fn) {
917                 return elem === fn.elem;
918             }).length;
919         };
920     }

 

posted @ 2013-12-01 21:47  LukeLin  阅读(1109)  评论(0编辑  收藏  举报