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 }