JQuery動畫能夠實現很是多的效果,而且支持擴展動畫效果,其中 http://easings.net/ 在基於JQuery上做了很是有用的動畫擴展,尤爲在一些曲線或拋物線上沒有這些公式很難作出理想的動畫來。css
JQuery動畫的底層實現核心思路是把整個區間分割成n個時間段,按時間動畫關係函數來計算出當時所在移動(變化)的區間比率值。
這是一個easings自由掉落動畫曲線,橫向爲時間,縱向爲移動(變化)區間,在不一樣的時間裏計算出對應的移動(變化)區間比例值便可實現對應的動畫效果。
下面來看看底層部分代碼:app
動畫入口代碼:ide
jQuery.fn.extend({ fadeTo: function( speed, to, easing, callback ) { // show any hidden elements after setting opacity to 0 return this.filter( isHidden ).css( "opacity", 0 ).show() // animate to the value specified .end().animate({ opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations resolve immediately if ( empty ) { anim.stop( true ); } }; return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { var stopQueue = function( hooks ) { var stop = hooks.stop; delete hooks.stop; stop( gotoEnd ); }; if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue && type !== false ) { this.queue( type || "fx", [] ); } return this.each(function() { var dequeue = true, index = type != null && type + "queueHooks", timers = jQuery.timers, data = jQuery._data( this ); if ( index ) { if ( data[ index ] && data[ index ].stop ) { stopQueue( data[ index ] ); } } else { for ( index in data ) { if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { stopQueue( data[ index ] ); } } } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { timers[ index ].anim.stop( gotoEnd ); dequeue = false; timers.splice( index, 1 ); } } // start the next in the queue if the last step wasn't forced // timers currently will call their complete callbacks, which will dequeue // but only if they were gotoEnd if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } }); } });
能夠看到 animate 方法是基於 Animation 對象實現的,但在建立 Animation 對象時須要一個 jQuery.speed( speed, easing, callback ) 返回值,這個返回值(便是動畫的時間與區間關係函數),在作動畫擴展時也是擴展這個返回值的種類。下面再來看看 jQuery.speed作了什麼:函數
jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.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; opt.complete = function() { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } }; return opt; }; jQuery.easing = { linear: function( p ) { return p; }, swing: function( p ) { return 0.5 - Math.cos( p*Math.PI ) / 2; } };
上面的代碼中作了一些初始化處理,其中也包含了一些默認的數據,包括默認支持動畫,以及默認動畫時長,咱們在調用動畫時就能夠在對應的參數上使用這些鍵名如:$('div').animate({top:'100px'}, 'slow');
在 jQuery.speed 處理中會提取 opt.duration 區間值,這個值就是時長以毫秒爲單位,常規默認值以下,若是不指定動畫時長則默認爲400毫秒。即在調用動畫時能夠寫 $('div').animate({top:'100px'});動畫
jQuery.fx.speeds = { slow: 600, fast: 200, // Default speed _default: 400 };
而後就是 Animation 對象,這個對象只是一個對jQuery.Tween對象的包裝,以及動畫定時器啓停處理,因此就不貼代碼了,直接看jQuery.Tween代碼this
function Tween( elem, options, prop, end, easing ) { return new Tween.prototype.init( elem, options, prop, end, easing ); } jQuery.Tween = Tween; Tween.prototype = { constructor: Tween, init: function( elem, options, prop, end, easing, unit ) { this.elem = elem; this.prop = prop; this.easing = easing || "swing"; this.options = options; this.start = this.now = this.cur(); this.end = end; this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); }, cur: function() { var hooks = Tween.propHooks[ this.prop ]; return hooks && hooks.get ? hooks.get( this ) : Tween.propHooks._default.get( this ); }, run: function( percent ) { var eased, hooks = Tween.propHooks[ this.prop ]; if ( this.options.duration ) { // 動畫函數調用 this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); } else { this.pos = eased = percent; } this.now = ( this.end - this.start ) * eased + this.start; if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } if ( hooks && hooks.set ) { hooks.set( this ); } else { Tween.propHooks._default.set( this ); } return this; } }; Tween.prototype.init.prototype = Tween.prototype;
在這段代碼中是最終調用動畫函數(代碼中已經添加註釋),這個動畫調用函數也是咱們擴展時須要遵循的,下面來講下擴展函數的參數:.net
jQuery.easing[ this.easing ](percent, this.options.duration * percent, 0, 1, this.options.duration)
從參數名上就能夠定義一些意思:
percent 當前動畫完成時長佔比佔比值(0% ~ 100%)固然這裏是以小數來表示如 0.6
this.options.duration * percent 當前動畫完成時長
0 返回最小值
1 返回最大值
this.options.duration 動畫總時長
須要說明下,這個函數不須要元素移動(變化)的樣式值,只須要動畫的時間進度,經過時間進度得出一個返回最大與最小範圍內的值經過這個值乘以移動(變化的總差值)能夠計算出本次的移動變化位置,從而實現動畫。注意當 percent 超過 100% 時返回值會隨着超過返回最大值。prototype
那麼在擴展動畫時應該是怎麼樣處理?code
jQuery.extend(jQuery.easing, { '動畫名' : function (percent, present, minReturn, maxReturn, duration){ return 處理函數公式 ; }, ... })
調用方式:orm
$('div').animate({top:'100px'}, 1000, '動畫名');
到這裏能夠說若是咱們不在基於JQuery上執行一些JQuery的擴展動畫函數時就能夠按照上面的參數說明來作,當前在作時必定要注意元素樣式單位值(最好保證一致性),還有須要注意元素的style屬性值與css文件的樣式值獲取方法不同,這裏就不一一說明。
下面給出一個自由掉落原生使用代碼,動畫函數取自easings,其它函數相似方式使用
(function () { //動畫函數 function easeOutBounce(n, e, t, a, i) { return(e /= i) < 1 / 2.75 ? a * (7.5625 * e * e) + t : e < 2 / 2.75 ? a * (7.5625 * (e -= 1.5 / 2.75) * e + .75) + t : e < 2.5 / 2.75 ? a * (7.5625 * (e -= 2.25 / 2.75) * e + .9375) + t : a * (7.5625 * (e -= 2.625 / 2.75) * e + .984375) + t; } var percent = 0, duration = 1000, t, div = document.createElement('div'), diff = 600; div.style.width = '10px'; div.style.height = '10px'; div.style.position = 'absolute'; div.style.top = '0px'; div.style.left = '50%'; div.style.backgroundColor = 'red'; t = setInterval(function () { var _percent = percent / 100, per = easeOutBounce(_percent, duration * _percent, 0, 1, duration); div.style.top = Math.round(diff * per) + 'px'; percent += 1; if (percent > 100) { clearInterval(t); } }, 20); document.body.appendChild(div); console.info(document.body); })();