jQuery的動畫模塊提供了包括隱藏顯示動畫、漸顯漸隱動畫、滑入劃出動畫,同時還支持構造複雜自定義動畫,動畫模塊用到了以前講解過的不少其它不少模塊,例如隊列、事件等等,css
$.animate()的用法以下:animate(prop,speed,easing,callback),參數以下:html
仍是舉個栗子:node
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script> <style> div{width: 200px;height: 50px;background: #cfc;} </style> </head> <body> <button>按鈕</button> <div></div> <script> $('button').click(function(){ $('div').animate({width:'400px',height:'100px',background:'#f00'},2000,'linear',function(){alert('動畫完成成功')}) }) </script> </body> </html>
咱們定義一個div和一個按鈕,再在按鈕上綁定一個事件,該事件回調用$.animate()來修改div的樣式,動畫完成後再執行一個回調函數,效果以下:jquery
挺好用的啊,很方便的,能夠用jQuery實現各類動畫,尺寸、位置、顏色,還能夠配合CSS3的transform屬性實現各類酷炫效果。數組
源碼分析緩存
jQuery內在執行$.animate()時會遍歷參數1也就是咱們要修改的樣式,而後分別建立一個對應的$.fx對象,實際上最終的動畫是在$.fx這個對象上運行的,另外jQuery內部會維護一個setInterval(),默認會每隔13毫秒執行一次樣式的修改,這樣看起來就和動畫同樣了。閉包
直接看代碼吧,$.animate()實現以下:ide
jQuery.fn.extend({ animate: function( prop, speed, easing, callback ) { //動畫入口函數,負責執行單個樣式的動畫, var optall = jQuery.speed( speed, easing, callback ); //調用$.speed修正運行時間、緩動函數,重寫完成回調函數 if ( jQuery.isEmptyObject( prop ) ) { //若是prop中沒有須要執行動畫的樣式 return this.each( optall.complete, [ false ] ); //則當即調用完成回調函數 } // Do not change referenced properties as per-property easing will be lost prop = jQuery.extend( {}, prop ); //複製一份動畫樣式的集合,以免改變原始動畫樣式集合,由於每一個動畫樣式的緩存函數最終會丟失。 function doAnimation() { //執行動畫的函數,後面再講解,咱們先把流程理解了 /*略*/ } return optall.queue === false ? //queue表示是否將當前動畫放入隊列 this.each( doAnimation ) : //若是optall.queue爲false,則調用.each()方法在每一個匹配元素上執行動畫函數doAnimation(); this.queue( optall.queue, doAnimation ); //不然調用方法.queue()將動畫函數doAnimation()插入選項queue指定的隊列中。 }, })
$.queue以前已經講過了,能夠看這個鏈接http://www.javashuo.com/article/p-kjugeufe-mg.html,因爲插入的是默認動畫,所以插入到隊列後就會自動執行的。函數
$.speed()是用於負責修正運行時間、緩動函數,重寫完成回調函數的,以下:源碼分析
jQuery.extend({ speed: function( speed, easing, fn ) { //負責修正運行時間、緩動函數,重寫完成回調函數。//speed:表示動畫持續時間、easing是所使用的緩動函數、fn是動畫完成時被調用的函數。 var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { //若是speed是對象,即$.fn.animate()只傳入了兩個參數的格式,則將該對象賦值給opt對象。 complete: fn || !fn && easing || //依次嘗試把最後一個參數做爲完成回調函數保存到complete中 jQuery.isFunction( speed ) && speed, duration: speed, //動畫持續時間 easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing //若是參數3非空,則設置easing爲參數2 }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : //修正duration選項 opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; // normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; //備份完成回調函數complete到old屬性中 opt.complete = function( noUnmark ) { //重寫complete函數,當前動畫完成時,自動取出下一個動畫函數。 if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } }; return opt; //最後返回opt }, })
$.animate函數很是靈活,能夠有以下形式:
這就是$.speed函數的功勞了。
回到$.animate函數內,當將doAnimation插入到隊列後,因爲是默認隊列就會自動執行該函數,doAnimation實現以下:
function doAnimation() { //負責遍歷動畫樣式集合,先修正、解析、備份樣式名,而後爲每一個樣式調用構造函數jQuery.fx()構造動畫 // XXX 'this' does not always have a nodeName when running the // test suite if ( optall.queue === false ) { //若是選項queue爲false,動畫將會當即執行 jQuery._mark( this ); } var opt = jQuery.extend( {}, optall ), //opt是完整選項集optall的副本; isElement = this.nodeType === 1, //isElement用於檢測當前元素是不是DOM元素 hidden = isElement && jQuery(this).is(":hidden"), //hidden表示當前元素是不是可見的 name, val, p, e, parts, start, end, unit, method; // will store per property easing and be used to determine when an animation is complete opt.animatedProperties = {}; //存放動畫樣式的緩存函數,當動畫完成後,屬性值被設置爲true。 for ( p in prop ) { //遍歷動畫樣式集合prop。修正、解析、備份樣式 ;p是每個樣式的名稱,好比:width height // property name normalization name = jQuery.camelCase( p ); ///將樣式名格式化爲駝峯式 if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; } val = prop[ name ]; //獲取每一個樣式的結束值和緩動函數 if ( jQuery.isArray( val ) ) { //若是樣式值是一個數組 opt.animatedProperties[ name ] = val[ 1 ]; //取第二個元素爲該樣式的緩動函數 val = prop[ name ] = val[ 0 ]; //修正第一個元素爲該樣式的結束值。 } else { opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; } if ( val === "hide" && hidden || val === "show" && !hidden ) { //若是值是hide但當前元素不可見,或者值是show但當前元素可見,表示不須要執行動畫就已經完成了 return opt.complete.call( this ); //直接觸發完成回調函數。這是一個優化措施 } if ( isElement && ( name === "height" || name === "width" ) ) { //備份或修正可能影響動畫效果的樣式,這些樣式將在動畫完成後被恢復。 // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and // overflowY are set to the same value opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width // animations on inline elements that are having width/height animated if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { // inline-level elements accept inline-block; // block-level elements need to be inline with layout if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { this.style.display = "inline-block"; } else { this.style.zoom = 1; } } } } if ( opt.overflow != null ) { //若是opt.overflow不爲空,即樣式值是width或height、 this.style.overflow = "hidden"; //則設置樣式overflow爲'hidden',即超出部分隱藏,這樣不影響其餘佈局 } for ( p in prop ) { //再次遍歷動畫樣式集合prop,爲每一個屬性執行動畫效果 p是每一個樣式名,好比:width、height e = new jQuery.fx( this, opt, p ); //調用構造函數jQuery.fn(elem,options,prop)建立一個標準動畫對象 val = prop[ p ]; //獲取設置的結束值,好比:500px,600px if ( rfxtypes.test( val ) ) { //若是樣式值val是toggle、show或hide // Tracks whether to show or hide based on private // data attached to the element method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); if ( method ) { jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); e[ method ](); } else { e[ val ](); } } else { //若是樣式值是數值型,則計算開始值start、結束值end、單位unit,而後開始執行動畫 parts = rfxnum.exec( val ); //rfxnum用於匹配樣式值中的運算符、數值、單位。rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, 格式:Array [ "700px", 符號, "數值", "單位" ] ,好比:'+=20px',匹配後:Array [ "+=20px", "+=", "20", "px" ] start = e.cur(); //獲取初始值,好比:200 if ( parts ) { //若是樣式值可以匹配正則parts,則說明是數值型,就計算開始值start、結束值end、單位unit,而後開始執行動畫。 end = parseFloat( parts[2] ); //end是傳入的樣式的數值,多是結束值,也可能要加上或減去的值 unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); //unit是最後的單位,若是沒有傳入則設置爲px單位 // We need to compute starting value if ( unit !== "px" ) { //若是結束值的單位不是默認單位px,則將開始值start換算爲與結束值end的單位一致的數值。 jQuery.style( this, p, (end || 1) + unit); start = ( (end || 1) / e.cur() ) * start; jQuery.style( this, p, start + unit); } // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { //若是樣式值以+= 或 -=開頭,則用開始值加上或減去給定的值, end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; //獲得結束值 } e.custom( start, end, unit ); //調用jQuery.fx.prototype.custom(from,to,unit)開始執行動畫。start是開始值,end是結束值,unit是單位。 } else { e.custom( start, val, "" ); //樣式值不是數值型時,仍然開始執行動畫,以支持插件擴展的動畫樣式。 } } } // For JS strict compliance return true; }
函數內首先調用new jQuery.fx()建立一個$.fx對象,最後調用該對象的custom開始執行動畫,$.fx的定義以下:
jQuery.extend({ fx: function( elem, options, prop ) { //jQuery構造函數,用於爲單個樣式構造動畫對象。elem:當前匹配元素、options:當前動畫的選項集、prop:參與動畫的當前樣式。 this.options = options; this.elem = elem; this.prop = prop; options.orig = options.orig || {}; } })
就是在當前實例上掛載幾個屬性,它的其它方法都定義在原型上的,例如cur和custom以下:
jQuery.fx.prototype = { // Get the current size cur: function() { //獲取this.elem元素this.prop樣式當前的值 if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { //若是當前DOM元素的this.prop屬性不爲空,且沒有style屬性或者有style屬性可是style[this.prop]爲null return this.elem[ this.prop ]; // } var parsed, r = jQuery.css( this.elem, this.prop ); //獲取該DOM元素的prop樣式的值,好比:200px // Empty strings, null, undefined and "auto" are converted to 0, // complex values such as "rotate(1rad)" are returned as is, // simple values such as "10px" are parsed to Float. return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; }, // Start an animation from one number to another custom: function( from, to, unit ) { //負責開始執行單個樣式的動畫。from:當前樣式的開始值、to:當前樣式的結束值、unit:當前樣式的單位。 var self = this, //self是當前樣式的jQuery.fx對象 fx = jQuery.fx; this.startTime = fxNow || createFxNow(); //動畫開始的時間,值爲當前時間,每次調用animate()方法時多個樣式的該值是同樣的。 this.end = to; //當前樣式的結束值 this.now = this.start = from; //當前樣式的單位。 this.pos = this.state = 0; //this.now:當前幀樣式值 、this.start:當前樣式的開始值 this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); //this.pos:差值百分比。this.state:已執行時間的百分比 function t( gotoEnd ) { //構造封裝了jQuery.fx.prototype.step()的單幀閉包動畫函數,函數t經過閉包機制引用jQuery.fx()對象。 return self.step( gotoEnd ); } t.queue = this.options.queue; //動畫類型,默認爲:fx t.elem = this.elem; //elem是當前匹配元素 t.saveState = function() { //記錄當前樣式動畫的狀態,在stop()中用的到 if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { jQuery._data( self.elem, "fxshow" + self.prop, self.start ); } }; if ( t() && jQuery.timers.push(t) && !timerId ) { //執行單幀閉包動畫函數t(gotoEnd),若是該函數返回true則表示動畫還沒有完成,則並將其插入全局動畫函數數組jQuery.timers中,若是timeId不存在,timeId是執行動畫的全局定時器。 timerId = setInterval( fx.tick, fx.interval ); //建立一個定時器。週期性的執行jQuery.fx.tick()方法,從而開始執行動畫。 } },
$.tick()內會執行setInterval,隔13毫秒就執行一次 t() 函數,t函數內會執行step()函數,該函數會計算匹配元素下一次的樣式值,以下:
writer by:大沙漠 QQ:22969969
jQuery.fx.prototype = { step: function( gotoEnd ) { //負責計算和執行當前樣式動畫的一幀。 var p, n, complete, t = fxNow || createFxNow(), //t是當前時間 done = true, elem = this.elem, //匹配元素 options = this.options; //選項 if ( gotoEnd || t >= options.duration + this.startTime ) { //動畫已完成的邏輯 若是參數totoEnd爲true(stop())調用時),或超出了動畫完成時間,最後會返回false this.now = this.end; //設置當前幀式值爲結束值 this.pos = this.state = 1; //設置已執行時間百分比和差值百分比爲1 this.update(); //調用update()更新樣式值。這三行代碼是用來修正動畫可能致使的樣式偏差的。 options.animatedProperties[ this.prop ] = true; //設置options.animatedProperties[ this.prop ]爲true,表示當前樣式動畫已經執行完成。 for ( p in options.animatedProperties ) { if ( options.animatedProperties[ p ] !== true ) { done = false; } } if ( done ) { // Reset the overflow if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { jQuery.each( [ "", "X", "Y" ], function( index, value ) { elem.style[ "overflow" + value ] = options.overflow[ index ]; }); } // Hide the element if the "hide" operation was done if ( options.hide ) { jQuery( elem ).hide(); } // Reset the properties, if the item has been hidden or shown if ( options.hide || options.show ) { for ( p in options.animatedProperties ) { jQuery.style( elem, p, options.orig[ p ] ); jQuery.removeData( elem, "fxshow" + p, true ); // Toggle data is no longer needed jQuery.removeData( elem, "toggle" + p, true ); } } // Execute the complete function // in the event that the complete function throws an exception // we must ensure it won't be called twice. #5684 complete = options.complete; if ( complete ) { options.complete = false; complete.call( elem ); } } return false; } else { //若是當前樣式動畫還沒有完成 // classical easing cannot be used with an Infinity duration if ( options.duration == Infinity ) { this.now = t; } else { n = t - this.startTime; //n = 當前時間-開始時間 = 已執行時間 this.state = n / options.duration; //已執行時間百分比 = 已執行時間/運行時間 // Perform the easing function, defaults to swing this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); //差值百分比=緩動函數(已執行時間百分比,已執行時間,0,1,運行時間) this.now = this.start + ( (this.end - this.start) * this.pos ); //當前幀樣式值=開始值+差值百分比*(結束值-開始值) } // Perform the next step of the animation this.update(); //更新樣式值。 } return true; } }
計算出樣式值後會將值保存到this.now上面,最終調用this.update()進行更新樣式,update源碼以下:
jQuery.fx.prototype = { update: function() { //更新當前樣式的值。內部經過jQuery.fx.step中的方法來更新樣式的值 if ( this.options.step ) { //若是當前的animate指定了step回調函數 this.options.step.call( this.elem, this.now, this ); //則調用該回調函數,參數分別是當前樣式的值和元素的引用 } ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); //不然調用默認方法jQuery.fx.step._default()直接設置內聯樣式(style屬性)或DOM屬性。 }, }
能夠看到優先經過$.fx.step來進行修改樣式,其次經過$.fx.step._default來修改,以下:
jQuery.extend( jQuery.fx, { step: { //最後的步驟:更新樣式 opacity: function( fx ) { //修正樣式opacity的設置行爲 jQuery.style( fx.elem, "opacity", fx.now ); }, _default: function( fx ) { //默認狀況下,經過直接設置內聯屬性(style屬性)或DOM屬性更新樣式值。 if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { //若是當前元素有style屬性,而且syle含有指定的樣式 fx.elem.style[ fx.prop ] = fx.now + fx.unit; //在style屬性上設置樣式值 } else { fx.elem[ fx.prop ] = fx.now; //不然在DOM元素上設置DOM屬性,例如:scrollTop、scrollLeft } } } })
能夠看到最終就是修改的元素的style來修改樣式的,或者直接修改DOM對象屬性。