jQuery的影片模塊提供了包括隱藏顯示影片、漸顯漸隱影片、滑入劃出影片,同時還支持構造復雜自定義影片,影片模塊用到了之前講解過的很多其它很多模塊,例如佇列、事件等等,
$.animate()的用法如下:animate(prop,speed,easing,callback),引數如下:
- prop ;物件, ;包含將要影片的樣式
- speed ;字串或數字 ;影片運行持續時間,單位毫秒,可以設定為"slow"、"normal"、"fast"等預定義時間
- easing ;字串 ;表示所使用的緩動函式
- callback ;回呼函式 ;當影片完成時被呼叫
還是舉個栗子:
<!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實作各種影片,尺寸、位置、顏色,還可以配合CSS3的transform屬性實作各種酷炫效果,
原始碼分析
jQuery內在執行$.animate()時會遍歷引數1也就是我們要修改的樣式,然后分別創建一個對應的$.fx物件,實際上最終的影片是在$.fx這個物件上運行的,另外jQuery內部會維護一個setInterval(),默認會每隔13毫秒執行一次樣式的修改,這樣看起來就和影片一樣了,
直接看代碼吧,$.animate()實作如下:
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之前已經講過了,可以看這個連接https://www.cnblogs.com/greatdesert/p/11670591.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函式非常靈活,可以有如下形式:
- animate(prop,speed,easing,callback)
- animate(prop,easing,callback)
- animate(prop,speed,callback)
- animate(prop,callback)
- animate(prop,obj)
這就是$.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物件屬性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/21643.html
標籤:jQuery
