前面的話
前面介紹過勻速運動的實現及注意事項,本文在勻速運動的基礎上,更進一步,實現各種變速運動,包括加速運動、減速運動、緩沖運動、重力運動和彈性運動
準備工作
勻速運動
在原生javascript中實現運動的主要工具是定時器,通過設置固定的間隔時間,使元素在確定的間隔時間內實現距離的變化。而運動變化的主要表現形式是距離的變化
例如,定時器頻率可如下列代碼所示,設置為30ms。每30ms對s的值進行更新,使其增加一個步長step的距離,來實現視覺上的元素運動效果
setInterval(function(){ s = s + step },30)
而step的值如何變化就決定了何種運動形式
s = v * t;
當step是一個恒定的值(如10),則說明相同時間間隔內,距離變化相同,說明速度是一個恒定的值,該運動為勻速運動
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> var timer; reset.onclick = function(){history.go();} btn.onclick = function(){ clearInterval(timer); //每30ms,位移變化10px var step = 10; //聲明當前值變量cur var cur; var target = parseFloat('500px'); timer = setInterval(function(){ cur = test.offsetLeft; //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值if(cur+step-target>0){ step = target - cur; } test.style.left = cur + step + 'px'; if(step == target - cur){ clearInterval(timer); } },20); } </script>
小數解析
在CSS解析中,是可以識別小數的;但在javascript中,不同的解析方式對于小數識別有區別
如果使用getComputedStyle或currentStyle是可以識別小數的,但是使用offset值,則返回對應的四舍五入值
[注意]IE7-瀏覽器不支持小數
<div id="test" style="height: 100px;width: 100.7px;"></div> <script> console.log(test.offsetWidth);//101 console.log(getComputedStyle(test).width);//'100.7px' </script>
在上面的代碼中,元素以100.7px的寬度進行渲染;但是,通過offsetWidth獲取的值是100.7四舍五入后的值101;通過getComputedStyle計算樣式獲取的值是實際渲染值100.7px
所以,為了保證結果準備盡量使用計算樣式,而不要使用offset值
function getCSS(obj,style){
if(window.getComputedStyle){
return getComputedStyle(obj)[style];
}
return obj.currentStyle[style];
}
加速運動
說到加速運動,必須要提到一個物理名詞——加速度
v = v0 + a*t;
s = (v0+v)*t/2 = v0*t + 1/2*a*t*t;
如果v0是初始速度,v1是定時器第n次經過20ms之后元素變化后的速度,v2是定時器第n+1次經過20ms之后元素變化后的速度
s1 = v0*t1 + 1/2*a*t1*t1;
s2 = v0*t2 + 1/2*a*t2*t2;
s2 - s1 = (t2-t1)(v0+ 1/2*a*(t2+t1)) = 0.02(v0+a*(0.02n+0.01))
所以,下列代碼中的步長step值是0.02(v0+a*(0.02n+0.01))
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
v0代表初始速度,a代表加速度,n代表定時器執行的次數
由于n的值是以+1的形式遞增,當a為正數時,step值不斷增加,則為加速運動;當a為負數時,step值不斷減小,則為減速運動
假設初始速度v0等于0,加速度a等于200,則step = 0.04(2n+1)
setInterval(function(){
s = s + step
},20)
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('500px'); clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //更新步長值 step = 0.04*(2*index+1); //更新當前值 cur = parseFloat(getCSS(test,'left')); //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if(cur+step-target>0){ step = target - cur; } //更新left值 test.style.left = cur + step + 'px'; //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(test.timer); } },20); } </script>
重力運動
重力運動是加速運動的特殊情況,相當于初始速度為0,加速度為9.8m/s2的特值情況
這時,涉及到長度單位m變換為像素單位px的過程
1cm = 37.8px
1m = 100cm
所以9.6m = 9.6*37.8*100px = 36288px
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
當v0=0,a=36288時,step = 7.2576(2n+1)
這里,我們把運動的距離設置為300px,實際上,轉換為常用長度單位時,只有8cm。如果,我們要以300px模擬8m的重力效果,則可以粗略地將加速度縮小為原來的1/100
此時,修正過的step值為0.072576(2n+1)
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;top:30px;"></div> <div style="background-color:red;height:1px;width:100px;position:absolute;top:300px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('300px'); clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //更新步長值 step = 0.072576*(2*index+1); //更新當前值 cur = parseFloat(getCSS(test,'top')); //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if(cur+step-target>0){ step = target - cur; } //更新top值 test.style.top = cur + step + 'px'; //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(test.timer); } },20); } </script>
減速運動
相對于加速運動來說,減速運動有一個臨界點的問題。如果元素運動到指定的位置前,速度已經減到0,則停到當前速度為0的位置
同樣以定時器20ms的頻率為例,位移變化的step值是0.02(v0+a*(0.02n+0.01))
假設初始速度v0為100px/s,加速度為-10,則step = 0.02(99.9-0.2n)
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('500px'); clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //更新步長值 step = 0.02*(99.9-0.2*index); if(step < 0){ clearInterval(test.timer); } //更新當前值 cur = parseFloat(getCSS(test,'left')); //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if(cur+step-target>0){ step = target - cur; } //更新left值 test.style.left = cur + step + 'px'; console.log(index,cur,step,target,test.style.left) //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(test.timer); } },20); } </script>
緩沖運動
緩沖運動是減速運動的一種特殊形式,指元素做減速運動,速度減到0時,恰好停在目標點位置
以定時器20ms的頻率為例
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
假設初始速度v0為100px/s,最終的v為0
v = v0 - a*t
s = (v0+v)/2*t
所以,a = -5000/s ,step = 2 - (2n+1)/s
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:500px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('500px'); clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //更新步長值 step = 2 - (2*index+1)/target; //更新當前值 cur = parseFloat(getCSS(test,'left')); //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if((cur+step-target)*step>0){ step = target - cur; } //更新left值 test.style.left = cur + step + 'px'; //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(test.timer); } },20); } </script>
加減速運動
加減速運動是加速運動和減速運動的結合。前半段運動時,做加速運動。到達指定點時,做減速運動,最終到達終點停止
step = 0.02(v0+a*(0.02n+0.01)) = 2/10000(100*v0+a(2n+1))
假設v0=0,最終速度v=100,距離s = 200
所以a = v*v/(2*s) = 5000/s = 25
則加速運動的step = (2n+1)/s =(2n+1)/200
在加速運動中,s=1/2*a*t*t;
所以加速運動總時間t = s/50 = 4,定時器運行次數n = t/0.02=200次
減速運動的step=0.02(v0-(2n+1)),此時的v0相應于加速運動結束時的瞬時速度100,a= -5000/s = -25
所以,減速運動的step=2-(2n+1)/s = 2-(2n+1)/200
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:blue;width:1px;height:100px;position:absolute;left:200px;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:400px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('400px'); clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //當index為200時,說明進行完一次運動,則將index置0 if(index == 200){ index = 0; }; //更新當前值 cur = parseFloat(getCSS(test,'left')); //更新步長值 //加速運動 if(cur < 200){ step =(2*index+1)/(target/2); }else{ //減速運動 step = 2-(2*index+1)/(target/2); } //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if((cur+step-target)*step>0){ step = target - cur; } //更新left值 test.style.left = cur + step + 'px'; //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(test.timer); } },20); } </script>
往復運動
往復運動相當于加減速運動的升級版。元素先加速后減速,當減速到0時,元素并不停止,而是做反向的先加速后減速運動,如此反復
加速運動和減速運動的公式與加減速運動的公式相同
加速運動:step = (2n+1)/s =(2n+1)/200
減速運動:step = 2-(2n+1)/s = 2-(2n+1)/200
<button id="btn">開始運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <div style="background-color:green;width:1px;height:100px;position:absolute;left:0px;"></div> <div style="background-color:blue;width:1px;height:100px;position:absolute;left:200px;"></div> <div style="background-color:red;width:1px;height:100px;position:absolute;left:400px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } reset.onclick = function(){history.go();} btn.onclick = function(){ //聲明定時器運行次數 var index=-1; //聲明步長值step var step; //聲明當前值cur var cur; //聲明目標值 var target = parseFloat('400px'); //聲明運動的次數,一個方向的加速和減速運動總共算一個運動 var num=0; clearInterval(test.timer); test.timer = setInterval(function(){ //更新定時器的工作次數 index++; //當index為200時,說明進行完一次運動,則將index置0 if(index == 200){ index = 0; num += 0.5; }; //更新當前值 cur = parseFloat(getCSS(test,'left')); //更新步長值 if(Math.floor(num)%2 == 0){ //加速運動 if(cur < 200){ step =(2*index+1)/200; }else{ //減速運動 step = 2-(2*index+1)/200; } }else{ //加速運動 if(cur > 200){ step =-(2*index+1)/200; }else{ //減速運動 step = (2*index+1)/200-2; } } //更新left值 test.style.left = cur + step + 'px'; },20); } </script>
變速函數
以上介紹的各種變速運動其中大部分代碼相同,只是步長公式不同而已。所以,我們可以把變速運動也封裝成一個函數形式,命名為varMove.js
function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function varMove(json){ var obj = json.obj; var attr = json.attr; var target = json.target; var type = json.type; var value = json.value; var fn = json.fn; //如果沒有建立定時器對象,則在obj下建立定時器對象 if(!obj.timers){obj.timers = {};} //清除定時器 if(obj.timers[attr]){return;} //聲明定時器運行次數 var index=-1; //聲明當前值變量cur var cur = parseFloat(getCSS(obj,attr)); //聲明距離為distance var distance= target - cur; //聲明運動的次數,一個方向的加速和減速運動總共算一個運動 var num=0; //開啟定時器 obj.timers[attr] = setInterval(function(){ //更新定時器的工作次數 index++; //獲取樣式當前值并賦值給cur cur = parseFloat(getCSS(obj,attr)); //根據不同的type值來設置步長 switch(type){ //如果type設置為'linear',則為勻速運動 case 'linear': //linear的value值為步長step step = Number(value) || 10; break; //如果type設置為'speedup',則為加速運動 case 'speedup': //'speedup'的value值為總時間t value = Number(value) || 2; step = (4*distance/(value*value*10000))*(2*index+1) break; //如果type設置為'gravity',則為重力運動 case 'gravity': step = 0.072576*(2*index+1); break; //如果type設置為'speeddown',則為減速運動 //'speeddown'的value值為初始速度v0 case 'speeddown': value = Number(value) || 100; step = (2/10000)*(100*value-(value*value)/(2*distance)*(2*index+1)) break; //如果type設置為'speedupAndDown',則為先加速后減速運動 //'speedupAndDown'的value值為總時間t case 'speedupAndDown': value = Number(value) || 2; //當index為25*value時,說明進行完一次運動,則將index置0 if(index == 25*value){ index = 0; }; //加速運動 if(cur < distance/2){ step =8*distance/(10000*value*value)*(2*index+1); }else{ //減速運動 step = distance/(25*value)-8*distance/(10000*value*value)*(2*index+1); } break; //如果type設置為'repeat',則為往復運動 //'repeat'的value值為一次運動(一次加速和一次減速)的時間 case 'repeat': value = Number(value) || 2; //當index為25*value時,說明進行完一次運動,則將index置0 if(index == 25*value){ index = 0; num += 0.5; }; if(Math.floor(num)%2 == 0){ //加速運動 if(cur < distance/2){ step =8*distance/(10000*value*value)*(2*index+1); }else{ //減速運動 step = distance/(25*value)-8*distance/(10000*value*value)*(2*index+1); } }else{ //加速運動 if(cur > distance/2){ step =-8*distance/(10000*value*value)*(2*index+1); }else{ //減速運動 step = 8*distance/(10000*value*value)*(2*index+1)-distance/(25*value); } } break; //如果沒有設置,則默認為'linear'勻速運動 default: step = 10; } //若步長設置值使得元素超過目標點時,將步長設置值更改為目標點值 - 當前值 if(((cur + step - target)*step > 0) && type != 'repeat'){ step = target - cur; } //將合適的步長值賦值給元素的樣式 obj.style[attr] = cur + step + 'px'; //當元素到達目標點后,停止定時器 if((step == target - cur) && type != 'repeat'){ clearInterval(obj.timers[attr]); obj.timers[attr] = 0; fn && fn.call(obj); } },20); }
下面以varMove函數為基礎,進行一些簡單應用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #box{ margin-bottom:10px; } #test{ height: 100px; width: 100px; background-color:lightblue; border-radius: 50%; position: absolute; left:0; } .backup{ height: 100px; width: 1px; position: absolute; } .backup:nth-child(1){ left:0px; background-color:red; } .backup:nth-child(2){ left:300px; background-color:green; } .backup:nth-child(3){ left:600px; background-color:blue; } </style> </head> <body> <div id="box"> <button id="btn1">勻速運動</button> <button id="btn2">加速運動</button> <button id="btn3">減速運動</button> <button id="btn4">加減速運動</button> <button id="btn5">往復運動</button> <button id="reset">還原</button> </div> <div id="test"></div> <div> <div class="backup"></div> <div class="backup"></div> <div class="backup"></div> </div> <script src="http://files.cnblogs.com/files/xiaohuochai/varMove.js"></script> <script> reset.onclick = function(){history.go();} btn1.onclick = function(){ varMove({obj:test,attr:'left',target:'600' }) } btn2.onclick = function(){ varMove({obj:test,attr:'left',target:'600',type:'speedup' }) } btn3.onclick = function(){ varMove({obj:test,attr:'left',target:'600',type:'speeddown' }) } btn4.onclick = function(){ varMove({obj:test,attr:'left',target:'600',type:'speedupAndDown' }) } btn5.onclick = function(){ varMove({obj:test,attr:'left',target:'600',type:'repeat' }) } </script> </body> </html>
文章列表