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 }