说好的缓动呢?

jQuery的缓动函数不给力,被逼自己写一个,既然要写,先把需求列好:

1. 动画类型有:linear, easeIn, easeOut, easeInOut

2. 可以在缓动过程中改变属性,也可以是调用函数,并改变函数的参数(后者是我的初衷,jq只支持属性,见 当0碰上0 )

 

动画是怎样产生的?学过Flash的人应该知道帧,帧就是一个画面,通常一秒25帧,也就一秒播放25个画面,如此快速的放过去,静态也成了动态(当然,前提是25个画面不是同一个画面,囧。。。)

 

难道做动画要画N多图,好吧,这叫“逐帧动画”,如果你很闲大可以这么干,因为这样做的效果绝对是顶级的,毫无瑕疵的。但是我一点都不闲,我画画的水平连幼儿园都教不了,所以这条路可以直接枪毙。

 

接下来进入正题,Flash中还有关键帧的概念,举个例子,关键帧A,物体box的宽高为100,关键帧B,box的宽高为50,A与B之间有25帧,接着我们从A播放到B,效果就是box在1秒内不断缩小,这叫“补间动画”。
 

这里涉及到三个东西:动画对象(box),动画属性(width, height),动画时间(1秒)

我们要做的事就是在某个时间点确定动画属性的值

 

常见的动画有四种类型,介绍一下:

          linear:线性动画,即匀速

        easeIn:速度从小到大,即淡入

    easeOut :速度从大到小,即淡出

  easeInOut:开始时速度从小到大,结束时速度从大到小,即淡入淡出
 

====================下面这段爱看不看,估计看着挺无聊的===========================

其实说到缓动,就不得不提Robert Penner,他发明了N多缓动公式,举个例子

/**
 * @param t 用掉的时间
 * @param b 初始值
 * @param c 总变化量,比如x的初始值为0,结束值为100,变化量就是100
 * @param d 动画总时长
 * @return 当前属性值
 */
function linear(t, b, c, d){ 
	return c * t / d + b; 
}

我还是解释一下吧:

  设当前变化量为X,则

  t / d = X / c,所以X = c * t / d,然后X + b就可以获得当前属性值
 

再看一个稍复杂的:

var easeInQuad =  function(t, b, c, d){
	return c * ( t /= d ) * t + b;
}

这个有淡入效果,也就是说动画开始时,值的变化量从小到大。

可以发现两者唯一的区别就是 t / d 和 (t /= d) * t,刚才说了t / d是一个>=0 && <=1的比值,暂取名为a,而(t /= d) * t就相当于Math.pow(a, 2)。
 

为什么要平方呢?

1. 首先a >= Math.pow(a, 2)是肯定的

2. 每次调用函数时,t / d 这个比值也是匀速变大的,比如第1次调用时是0.1(平方0.01),第2次调用时是0.2(平方0.04)等,那第10次调用时,肯定是1没错吧,这时候 c * 1 + b,动画就到此结束了

3. 第2点证明了比值越小,值的变化量就越小,比值越大,值的变化量就越大,如果不用平方而是三次方,那淡入效果就更明显了。

 

举个两个例子,可以看出一些规律,公式大体便是 当前属性值 = 总变化量 * 增量系数 + 初始值, 其中,增量系数根据不同算法各有不同。
 

====================继续往下看吧===========================
 

增量系数的算法,我就从 司徒正美 那拿过来用吧,他总结了一大堆,必须收藏啊!!

var tween = {
	linear : function(pos) {
		return 1;
	},
	easeInQuad : function(pos) {
		return Math.pow(pos, 2);
	},
	easeOutQuad : function(pos) {
		return -(Math.pow(( pos - 1), 2) - 1);
	},
	easeInOutQuad : function(pos) {
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(pos, 2);
		return -0.5 * ((pos -= 2) * pos - 2);
	},
	easeInCubic : function(pos) {
		return Math.pow(pos, 3);
	},
	easeOutCubic : function(pos) {
		return (Math.pow(( pos - 1), 3) + 1);
	},
	easeInOutCubic : function(pos) {
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(pos, 3);
		return 0.5 * (Math.pow(( pos - 2), 3) + 2);
	},
	easeInQuart : function(pos) {
		return Math.pow(pos, 4);
	},
	easeOutQuart : function(pos) {
		return -(Math.pow(( pos - 1), 4) - 1)
	},
	easeInOutQuart : function(pos) {
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(pos, 4);
		return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2);
	},
	easeInQuint : function(pos) {
		return Math.pow(pos, 5);
	},
	easeOutQuint : function(pos) {
		return (Math.pow(( pos - 1), 5) + 1);
	},
	easeInOutQuint : function(pos) {
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(pos, 5);
		return 0.5 * (Math.pow(( pos - 2), 5) + 2);
	},
	easeInSine : function(pos) {
		return -Math.cos(pos * (Math.PI / 2)) + 1;
	},
	easeOutSine : function(pos) {
		return Math.sin(pos * (Math.PI / 2));
	},
	easeInOutSine : function(pos) {
		return (-.5 * (Math.cos(Math.PI * pos) - 1));
	},
	easeInExpo : function(pos) {
		return (pos == 0) ? 0 : Math.pow(2, 10 * ( pos - 1));
	},
	easeOutExpo : function(pos) {
		return (pos == 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
	},
	easeInOutExpo : function(pos) {
		if(pos == 0)
			return 0;
		if(pos == 1)
			return 1;
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(2, 10 * ( pos - 1));
		return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
	},
	easeInCirc : function(pos) {
		return -(Math.sqrt(1 - (pos * pos)) - 1);
	},
	easeOutCirc : function(pos) {
		return Math.sqrt(1 -  Math.pow(( pos - 1), 2))
	},
	easeInOutCirc : function(pos) {
		if((pos /= 0.5) < 1)
			return -0.5 * (Math.sqrt(1 - pos * pos) - 1);
		return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
	},
	easeOutBounce : function(pos) {
		if((pos) < (1 / 2.75)) {
			return (7.5625 * pos * pos);
		} else if(pos < (2 / 2.75)) {
			return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
		} else if(pos < (2.5 / 2.75)) {
			return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
		} else {
			return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
		}
	},
	easeInBack : function(pos) {
		var s = 1.70158;
		return (pos) * pos * ((s + 1) * pos - s);
	},
	easeOutBack : function(pos) {
		var s = 1.70158;
		return ( pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
	},
	easeInOutBack : function(pos) {
		var s = 1.70158;
		if((pos /= 0.5) < 1)
			return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
		return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
	},
	elastic : function(pos) {
		return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
	},
	swingFromTo : function(pos) {
		var s = 1.70158;
		return ((pos /= 0.5) < 1) ? 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
	},
	swingFrom : function(pos) {
		var s = 1.70158;
		return pos * pos * ((s + 1) * pos - s);
	},
	swingTo : function(pos) {
		var s = 1.70158;
		return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
	},
	bounce : function(pos) {
		if(pos < (1 / 2.75)) {
			return (7.5625 * pos * pos);
		} else if(pos < (2 / 2.75)) {
			return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
		} else if(pos < (2.5 / 2.75)) {
			return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
		} else {
			return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
		}
	},
	bouncePast : function(pos) {
		if(pos < (1 / 2.75)) {
			return (7.5625 * pos * pos);
		} else if(pos < (2 / 2.75)) {
			return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
		} else if(pos < (2.5 / 2.75)) {
			return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
		} else {
			return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
		}
	},
	easeFromTo : function(pos) {
		if((pos /= 0.5) < 1)
			return 0.5 * Math.pow(pos, 4);
		return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2);
	},
	easeFrom : function(pos) {
		return Math.pow(pos, 4);
	},
	easeTo : function(pos) {
		return Math.pow(pos, 0.25);
	},
	linear : function(pos) {
		return pos
	},
	sinusoidal : function(pos) {
		return (-Math.cos(pos * Math.PI) / 2) + 0.5;
	},
	reverse : function(pos) {
		return 1 - pos;
	},
	mirror : function(pos, transition) {
		transition = transition || tween.sinusoidal;
		if(pos < 0.5)
			return transition(pos * 2);
		else
			return transition(1 - ( pos - 0.5) * 2);
	},
	flicker : function(pos) {
		var pos = pos + (Math.random() - 0.5) / 5;
		return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
	},
	wobble : function(pos) {
		return (-Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5;
	},
	pulse : function(pos, pulses) {
		return (-Math.cos((pos * ((pulses || 5) - .5) * 2) * Math.PI) / 2) + .5;
	},
	blink : function(pos, blinks) {
		return Math.round(pos * (blinks || 5)) % 2;
	},
	spring : function(pos) {
		return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
	},
	none : function(pos) {
		return 0;
	}
};

  

你会发现有Quad,Cubic,Quart之类的字眼,这些到底是啥意思呢,这里我给出Flash的Tweener 官方文档,一看便知了(如果下面是空白,表示在缓冲,为了看到这么好的演示,耐心等会吧)


根据上述知识点,我的动画函数便出炉了

/**
 * @param {HTMLElement/Function} obj 属性时:动画对象; 方法时: 方法引用
 * @param {Object} params 属性时:{属性: 结束值}; 方法时:{startArgs: 初始参数[数组], endArgs: 结束参数[数组]};
 * @param {Number} duration 动画总时长,单位为毫秒
 * @param {String} type 动画类型,默认为linear,具体值参考tween下的属性名
 * @param {Function} callback 动画结束时执行的回调函数
 */
var animate = function(obj, params, duration, type, callback){
	window.requestAnimationFrame = (function(){
		return  window.requestAnimationFrame ||
		  window.webkitRequestAnimationFrame ||
		  window.mozRequestAnimationFrame    ||
		  window.oRequestAnimationFrame      ||
		  window.msRequestAnimationFrame     ||
		  function(callback){
			window.setTimeout(callback, 1000 / 60);
          };
      })();

	var tween = {
		linear : function(pos) {
			return 1;
		},
		easeInQuad : function(pos) {
			return Math.pow(pos, 2);
		},
		easeOutQuad : function(pos) {
			return -(Math.pow(( pos - 1), 2) - 1);
		},
		easeInOutQuad : function(pos) {
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(pos, 2);
			return -0.5 * ((pos -= 2) * pos - 2);
		},
		easeInCubic : function(pos) {
			return Math.pow(pos, 3);
		},
		easeOutCubic : function(pos) {
			return (Math.pow(( pos - 1), 3) + 1);
		},
		easeInOutCubic : function(pos) {
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(pos, 3);
			return 0.5 * (Math.pow(( pos - 2), 3) + 2);
		},
		easeInQuart : function(pos) {
			return Math.pow(pos, 4);
		},
		easeOutQuart : function(pos) {
			return -(Math.pow(( pos - 1), 4) - 1)
		},
		easeInOutQuart : function(pos) {
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(pos, 4);
			return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2);
		},
		easeInQuint : function(pos) {
			return Math.pow(pos, 5);
		},
		easeOutQuint : function(pos) {
			return (Math.pow(( pos - 1), 5) + 1);
		},
		easeInOutQuint : function(pos) {
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(pos, 5);
			return 0.5 * (Math.pow(( pos - 2), 5) + 2);
		},
		easeInSine : function(pos) {
			return -Math.cos(pos * (Math.PI / 2)) + 1;
		},
		easeOutSine : function(pos) {
			return Math.sin(pos * (Math.PI / 2));
		},
		easeInOutSine : function(pos) {
			return (-.5 * (Math.cos(Math.PI * pos) - 1));
		},
		easeInExpo : function(pos) {
			return (pos == 0) ? 0 : Math.pow(2, 10 * ( pos - 1));
		},
		easeOutExpo : function(pos) {
			return (pos == 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
		},
		easeInOutExpo : function(pos) {
			if(pos == 0)
				return 0;
			if(pos == 1)
				return 1;
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(2, 10 * ( pos - 1));
			return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
		},
		easeInCirc : function(pos) {
			return -(Math.sqrt(1 - (pos * pos)) - 1);
		},
		easeOutCirc : function(pos) {
			return Math.sqrt(1 -  Math.pow(( pos - 1), 2))
		},
		easeInOutCirc : function(pos) {
			if((pos /= 0.5) < 1)
				return -0.5 * (Math.sqrt(1 - pos * pos) - 1);
			return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
		},
		easeOutBounce : function(pos) {
			if((pos) < (1 / 2.75)) {
				return (7.5625 * pos * pos);
			} else if(pos < (2 / 2.75)) {
				return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
			} else if(pos < (2.5 / 2.75)) {
				return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
			} else {
				return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
			}
		},
		easeInBack : function(pos) {
			var s = 1.70158;
			return (pos) * pos * ((s + 1) * pos - s);
		},
		easeOutBack : function(pos) {
			var s = 1.70158;
			return ( pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
		},
		easeInOutBack : function(pos) {
			var s = 1.70158;
			if((pos /= 0.5) < 1)
				return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
			return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
		},
		elastic : function(pos) {
			return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
		},
		swingFromTo : function(pos) {
			var s = 1.70158;
			return ((pos /= 0.5) < 1) ? 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
		},
		swingFrom : function(pos) {
			var s = 1.70158;
			return pos * pos * ((s + 1) * pos - s);
		},
		swingTo : function(pos) {
			var s = 1.70158;
			return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
		},
		bounce : function(pos) {
			if(pos < (1 / 2.75)) {
				return (7.5625 * pos * pos);
			} else if(pos < (2 / 2.75)) {
				return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
			} else if(pos < (2.5 / 2.75)) {
				return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
			} else {
				return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
			}
		},
		bouncePast : function(pos) {
			if(pos < (1 / 2.75)) {
				return (7.5625 * pos * pos);
			} else if(pos < (2 / 2.75)) {
				return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75);
			} else if(pos < (2.5 / 2.75)) {
				return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375);
			} else {
				return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375);
			}
		},
		easeFromTo : function(pos) {
			if((pos /= 0.5) < 1)
				return 0.5 * Math.pow(pos, 4);
			return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2);
		},
		easeFrom : function(pos) {
			return Math.pow(pos, 4);
		},
		easeTo : function(pos) {
			return Math.pow(pos, 0.25);
		},
		linear : function(pos) {
			return pos
		},
		sinusoidal : function(pos) {
			return (-Math.cos(pos * Math.PI) / 2) + 0.5;
		},
		reverse : function(pos) {
			return 1 - pos;
		},
		mirror : function(pos, transition) {
			transition = transition || tween.sinusoidal;
			if(pos < 0.5)
				return transition(pos * 2);
			else
				return transition(1 - ( pos - 0.5) * 2);
		},
		flicker : function(pos) {
			var pos = pos + (Math.random() - 0.5) / 5;
			return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
		},
		wobble : function(pos) {
			return (-Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5;
		},
		pulse : function(pos, pulses) {
			return (-Math.cos((pos * ((pulses || 5) - .5) * 2) * Math.PI) / 2) + .5;
		},
		blink : function(pos, blinks) {
			return Math.round(pos * (blinks || 5)) % 2;
		},
		spring : function(pos) {
			return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
		},
		none : function(pos) {
			return 0;
		}
	};

	animate = function(obj, params, duration, type, callback){
		var startValue = {},//初始值
			changeValue = null,//总变化量
			startTime = new Date().getTime(),
			ease = tween[type || 'linear'];
			
		if(typeof obj !== 'function'){
			obj = obj.style;
			for(var name in params){
				startValue[name] = getStyle(obj, name);
			}
			changeValue = {};
			for(name in params){
				changeValue[name] = params[name] - startValue[name];
			}
		}else{
			changeValue = [];
			var startArgs = params.startArgs,
				endArgs = params.endArgs,
				toString = Object.prototype.toString;
			for(var i = 0, len = startArgs.length; i < len; i++){
				changeValue[i] = endArgs[i] - startArgs[i];
			}
		}
		
		var run = function(){
			var timeStamp = new Date().getTime() - startTime,
				factor = ease(timeStamp / duration);//增量系数
			if(!(toString.call(changeValue) === '[object Array]')){
				for(name in params){
					obj[name] = (changeValue[name] * factor + startValue[name]) + 'px';//套公式吧,亲
				}
			}else{
				var curArgs = [];
				for(i = 0; i < len; i++){
					curArgs[i] = changeValue[i] * factor + startArgs[i];
				}
				obj.apply(null, curArgs);
			}
			if(factor < 1){
				window.requestAnimationFrame(run);	
			}else{
				if(callback)callback();
			}
		};
		window.requestAnimationFrame(run);	
	}
	animate(obj, params, duration, type, callback);
};

var getStyle = function(el, style){
	var value = null;
	if(window.defaultView){
		value = window.getComputedStyle(el, null).getPropertyValue(style);
	}else{
		style = style.replace(/\-(\w)/g, function($0, $1){
			return $1.toUpperCase();
		});
		value = el.currentStyle[style];
		if(value === 'auto'){
			value = '0';
		}
	}
	return parseFloat(value) || 0;
};

  

posted @ 2011-09-29 01:12  越己  阅读(2944)  评论(33编辑  收藏  举报