Skip to main content
 首页 » 编程设计

jQuery源码分析之Animation模块

2022年07月16日48虾米哥
  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     }

本文参考链接:https://www.cnblogs.com/webFrontDev/p/3453021.html