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 }