说好的缓动呢?
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; };