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); })();