javascript的緩動(dòng)效果(第2部分)
這部分對(duì)原先的緩動(dòng)函數(shù)進(jìn)行抽象化,并結(jié)合緩動(dòng)公式進(jìn)行強(qiáng)化。成品的效果非常驚人逆天。走過(guò)路過(guò)不要錯(cuò)過(guò)。
好了,打諢到此為止。普通的加速減速是難以讓人滿意的,為了實(shí)現(xiàn)彈簧等讓人眼花繚亂的效果必須動(dòng)用緩動(dòng)公式。我見過(guò)兩套緩動(dòng)公式,一套是早期Robert Penner大神的緩動(dòng)公式,內(nèi)置到tween類中,不過(guò)現(xiàn)在人們?cè)絹?lái)越推薦tweenlite這個(gè)新秀了。另一套是script.aculo.us與mootools里面的,由于mootools可稱之為prototype的升級(jí)版,script.aculo.us則是基于prototype,我們就把它們并稱為prototype流派。與flash流派最大的不同是,它們封裝得更好,并只需傳入一個(gè)參數(shù)(0~1的小數(shù)),并且擁有嚴(yán)密的隊(duì)列機(jī)制來(lái)調(diào)用各種回調(diào)函數(shù)。如在回調(diào)函數(shù)設(shè)置元素的長(zhǎng)寬,就弄成Scale特效,利用它我們進(jìn)一步制作SlideUp,SlideDown,Squish等復(fù)合特效。
我們先來(lái)看flash流派的緩動(dòng)公式,它們基本都有如下四個(gè)參數(shù)。
- t:timestamp,指緩動(dòng)效果開始執(zhí)行到當(dāng)前幀開始執(zhí)行時(shí)經(jīng)過(guò)的時(shí)間段,單位ms
- b:beginning position,起始位置
- c:change,要移動(dòng)的距離,就是終點(diǎn)位置減去起始位置。
- d: duration ,緩和效果持續(xù)的時(shí)間。
我們把這四個(gè)參數(shù)傳入Robert Penner大神的緩動(dòng)公式,它就會(huì)計(jì)算出當(dāng)前幀物體移動(dòng)的位置。我們對(duì)比原來(lái)的函數(shù)來(lái)改寫。
var transition = function(el){
transition.linear = function(t,b,c,d){ return c*t/d + b; };//免費(fèi)提供一個(gè)緩動(dòng)公式(勻速運(yùn)動(dòng)公式)
el.style.position = "absolute";
var options = arguments[1] || {},
begin = getCoords(el).left,//開始位置
change = parseFloat(getStyle(_("taxiway"),"width")) - parseFloat(getStyle(el,"width")),//要移動(dòng)的距離
duration = options.duration || 500,//緩動(dòng)效果持續(xù)時(shí)間
ease = options.ease || transition.linear,//要使用的緩動(dòng)公式
end = begin + change,//結(jié)束位置
startTime = new Date().getTime();//開始執(zhí)行的時(shí)間
(function(){
setTimeout(function(){
var newTime = new Date().getTime(),//當(dāng)前幀開始的時(shí)間
timestamp = newTime - startTime;//逝去時(shí)間
el.style.left = ease(timestamp,begin,change,duration) + "px";//移動(dòng)
if(duration <= timestamp){
el.style.left = end + "px";
}else{
setTimeout(arguments.callee,25);//每移動(dòng)一次停留25毫秒
}
},25)
})()
}
接著是各種緩動(dòng)公式大閱兵,共分為十一大類,除了linear。其他類又分為三種。
- easeIn方法控制補(bǔ)間如何從開始到最高速度。
- easeOut 方法控制補(bǔ)間減速并停在目標(biāo)位置
- easeInOut方法同時(shí)控制上述兩者。
具體公式見下面(共31種)。
//***********@author:Robert Penner and cloudgamer*************
//http://www.rzrgm.cn/cloudgamer/archive/2009/01/06/Tween.html
var Tween = {
Linear: function(t,b,c,d){ return c*t/d + b; },
Quad: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t + b;
},
easeOut: function(t,b,c,d){
return -c *(t/=d)*(t-2) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
}
},
Cubic: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
},
Quart: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t + b;
},
easeOut: function(t,b,c,d){
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
}
},
Quint: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
}
},
Sine: {
easeIn: function(t,b,c,d){
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOut: function(t,b,c,d){
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOut: function(t,b,c,d){
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
}
},
Expo: {
easeIn: function(t,b,c,d){
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOut: function(t,b,c,d){
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOut: function(t,b,c,d){
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
},
Circ: {
easeIn: function(t,b,c,d){
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOut: function(t,b,c,d){
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
}
},
Elastic: {
easeIn: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b);
},
easeInOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
}
},
Back: {
easeIn: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
}
},
Bounce: {
easeIn: function(t,b,c,d){
return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
},
easeOut: function(t,b,c,d){
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOut: function(t,b,c,d){
if (t < d/2) return Tween.Bounce.easeIn(t*2, 0, c, d) * .5 + b;
else return Tween.Bounce.easeOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
}
}
}
<div id="taxiway">
<div id="move" onclick="transition(this,{ease:Tween.Bounce.easeOut})"></div>
</div>
但我不喜歡flash流派的緩動(dòng)公式,為了使用prototype流派的緩動(dòng)公式,我進(jìn)一步改進(jìn)與抽象化我的緩動(dòng)函數(shù)
//******************@author : 司徒正美************
var transition = function(el){
el.style.position = "absolute";
var options = arguments[1] || {},
begin = options.begin,//開始位置
change = options.change,//變化量
duration = options.duration || 500,//緩動(dòng)效果持續(xù)時(shí)間
field = options.field,//必須指定,基本上對(duì)top,left,width,height這個(gè)屬性進(jìn)行設(shè)置
ftp = options.ftp || 50,
onStart = options.onStart || function(){},
onEnd = options.onEnd || function(){},
ease = options.ease,//要使用的緩動(dòng)公式
end = begin + change,//結(jié)束位置
startTime = new Date().getTime();//開始執(zhí)行的時(shí)間
onStart();
(function(){
setTimeout(function(){
var newTime = new Date().getTime(),//當(dāng)前幀開始的時(shí)間
timestamp = newTime - startTime,//逝去時(shí)間
delta = ease(timestamp / duration);
el.style[field] = Math.ceil(begin + delta * change) + "px"
if(duration <= timestamp){
el.style[field] = end + "px";
onEnd();
}else{
setTimeout(arguments.callee,1000/ftp);
}
},1000/ftp)
})()
}
| 參數(shù) | 類型 | 說(shuō)明 |
|---|---|---|
| el | element | 必需,為頁(yè)面元素 |
| begin | number | 必需,開始的位置 |
| change | number | 必需,要移動(dòng)的距離 |
| duration | number | 可選,緩動(dòng)效果持續(xù)時(shí)間,默認(rèn)是500ms。建議取300~1000ms。 |
| field | string | 必需,要發(fā)生變化的樣式屬性。請(qǐng)?jiān)趖op,left,bottom,right,width與height中選擇。 |
| ftp | number | 可選,每秒進(jìn)行多少幀動(dòng)畫,默認(rèn)50幀,保證流暢播放。一些參考資料,日本動(dòng)畫1秒36幀,中國(guó)卡通24幀,賽車游戲60幀。 |
| ease | function | 必需,緩動(dòng)公式,參數(shù)為0~1之間的數(shù)??蓞⒖嘉蚁旅娼o出的45條公式。 |
| onStart | function | 可選,在開始時(shí)執(zhí)行。 |
| onEnd | function | 可選,在結(jié)束時(shí)執(zhí)行。 |
prototype流派的緩動(dòng)公式,只需一個(gè)參數(shù)(增至45種)
var tween = {
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
},
full: function(pos){
return 1
}
}
除了這45條公式外,我們還可以制定自己的緩動(dòng)公式。正如我在上面表格中提到, 它在運(yùn)行過(guò)程是不執(zhí)行回調(diào)函數(shù)時(shí),但你們可以在運(yùn)行框中看到,我可以實(shí)現(xiàn)一邊移動(dòng)一邊記錄點(diǎn)的坐標(biāo)。這是怎樣實(shí)現(xiàn)的呢? 我們只要把上面的緩動(dòng)公式的任何一條塞進(jìn)一個(gè)只有一個(gè)參數(shù)的函數(shù)就行了。當(dāng)然此函數(shù)要有返回,供繼續(xù)向下調(diào)用。以下就是一個(gè)模板:
var myTween = function(pos){ //緩動(dòng)公式
var value = tween[ease](pos);
//***********這上面是固定的**************
indicator.style.display = "block";
marker.style.display = "block";
marker.style.left = Math.round((pos*200))+'px';
marker.style.bottom = Math.round(((value*200)-min)*factor)+'px';
label.innerHTML = Math.round((pos*200))+'px';
//************這下面是固定的*************
return value;
}
更多示例,不懂再留言給我。
<div class="taxiway">
<div class="move" onclick="transition(this,{field:'left',begin:parseFloat(getCoords(this).left),change:700,ease:tween.bouncePast})"></div>
</div>
<div class="taxiway">
<div class="move" onclick="transition(this,{field:'width',begin:parseFloat(getStyle(this,'width')),change:300,ease:tween.spring})"></div>
</div>
posted on 2009-09-17 04:18 司徒正美 閱讀(12533) 評(píng)論(34) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)