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     }
相關文章
相關標籤/搜索