javascript運動系列第五篇——緩衝運動和彈性運動
前面的話
緩衝運動指的是減速運動,減速到0的時候,元素正好停在目標點。而彈性運動同樣是減速運動,但元素並不是直接停在目標點,而是在目標點附近彈幾下再停止。本文將以一種新的思路來詳細介紹緩衝運動和彈性運動
緩衝運動
在變速運動中,曾經用物理學的知識實現過緩衝運動。緩衝運動實際上就是減速運動的一種特殊形式,指元素做減速運動,速度減到0時,恰好停在目標點位置,學名叫加速度恆定的勻減速運動
現在使用另一種思路,樣式值等於當前值加上步長值,步長值的變化決定了運動的形式
test.style.left = cur + step + 'px';
元素距離目標點越近,速度越小,所以step可以寫成如下公式
step = (target - cur)*k;
k表示減速係數,k不能隨便取值,當k值過大時,效果將很不明顯。因為step取值過大,使得定時器僅僅工作幾次,元素就達到了目標點
當k值過小時,也會出現問題。cur值取得的值是當前的計算樣式,而計算樣式的值由於計算機儲存的限制,並不能儲存全部小數位數,IE9+瀏覽器可以儲存2位小數,IE8-瀏覽器不可以儲存小數,其他瀏覽器可以儲存3位小數。這樣,在計算中,當step值為0.0009(chrome),或0.009(IE9+),或0.1(IE8-)時,test.style.left = cur + step + 'px'將不起作用,樣式值將不會改變
這時,只要將當前值向上取整(當前值為負數時,向下取整)即可
<button id="btn1">勻速運動</button> <button id="btn2">緩衝運動</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]; } function easyMove(json){ var obj = json.obj; var attr = json.attr; //樣式預設值為'left' attr = attr || 'left'; var value = json.value; var target = json.target; //目標點預設值為'200' target = Number(target) || 200; //宣告步長值step var step; //聲明當前值cur var cur = parseFloat(getCSS(test,'left')); var type = json.type; var fn = json.fn; //如果沒有建立定時器物件,則在obj下建立定時器物件 if(!obj.timers){obj.timers = {};} //清除定時器 if(obj.timers[attr]){return;} //開啟定時器 obj.timers[attr] = setInterval(function(){ //更新當前值 cur = parseFloat(getCSS(test,'left')); switch(type){ case 'linear': step = Number(value) || 10; break; case 'buffer': //處理到不了目標點的問題 cur = cur > 0 ? Math.ceil(cur) : Math.floor(cur); value = Number(value) || 0.1; //更新步長值 step = (target - cur)*0.1; break; default: step = 10; } //若步長設定值使得元素超過目標點時,將步長設定值更改為目標點值 - 當前值 if((cur + step - target)*step > 0){ step = target - cur; } //將合適的步長值賦值給元素的樣式 obj.style[attr] = cur + step + 'px'; //當元素到達目標點時,停止定時器 if(step == target - cur){ clearInterval(obj.timers[attr]); obj.timers[attr] = 0; fn && fn.call(obj); } },20); } reset.onclick = function(){history.go();} btn1.onclick = function(){ easyMove({obj:test,target:500}) } btn2.onclick = function(){ easyMove({obj:test,target:500,type:'buffer'}) } </script>
彈性運動
要理解彈性運動,可以想象一個被拉伸的彈性繩子綁住的小球,小球向綁繩子的木樁運動,並圍繞木樁左右運動,最終由於空氣阻力的影響,停在木樁處
接下來,我們對小球的運動過程進行詳細分析
【1】小球在起始點時,受到繩子的彈力f=k*s,k為彈性係數,s=max為小球和木樁的距離。於是,小球向右運動
【2】小球在向右運動的過程中,由於距離木樁的距離s逐漸變小,f=k*s,所以彈力逐漸變小,而小球受到的阻力fx是恆定的。所以,小球做加速度減小的加速運動(加得越來越慢)
【3】當f = fx時,小球此時的加速度為0。此後,小球開始向右做加速度增大的減速運動(減得越來越快)
【4】當小球運動到木樁處時,彈力突然消失。這時,只剩餘空氣阻力
【5】小球繼續向右運動,小球有反向的彈力和阻力。阻力一直不變,而彈力越來越大。所以,小球做加速度增大的減速運動(減得越來越快)
【6】最終,小球運動到右邊最遠處時,速度減成0。此時,小球不受阻力,只受到反向的彈力。於是,小球開始向左做加速度減小的加速運動
【7】在向左運動的過程中,小球受到了向右的阻力,於是,加速度繼續減小
【8】在某一時刻,阻力等於彈力,加速度減成0。此後,阻力將大於彈力,小球將做減速運動
【9】若小球運動到木樁處,速度減成0,則運動停止。否則,小球將繼續向左做減速運動
【10】在某一時刻,小球速度減成0。此後,小球向右做先加速再減速的運動。若小球運動到木樁處,速度減成0,則運動停止。否則,小球將繼續向右做減速運動。接下來,將重複第6步的內容
如果要按照物理學公式實現彈性運動時,元素的運動涉及到變加速運動,需要用到微積分的知識,處理起來相對複雜
距離分析
下面用一個簡單的思路來實現彈性運動。如果以距離來分析,彈性運動就是每一次運動距離不斷減小的運動
例如,元素剛開始時距離目標點為100。第一次運動向右運動150,到達150處;第二次運動向左運動75,到達75處;第三次運動向右運動37.5,到達112.5處;第四次運動向左運動18.75,到達93.75。以此反覆,最終無限接近於目標點100
set = init + target + len*k;
k*=0.5;
其中init為樣式初始值,target為目標值,len為彈性最遠值,k為衰減係數
由於利用距離分析實現的彈性運動,實際上是一個無限接近於目標點的運動,需要為其設定停止條件,並將其位置置於目標點
當len*k的四捨五入值等於0時,停止運動
if(Math.round(len*k) == 0){
obj.style[attr] = target + 'px';
clearInterval(obj.timer);
}
<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]; } function elasticMove(json){ var obj = json.obj; var attr = json.attr; //樣式預設值為'left' attr = attr || 'left'; var target = json.target; //目標點預設值為'200' target = Number(target) || 200; //宣告元素從目標點到最遠點的距離 var len = json.len; //預設值為target的1/5 len = len || target/5; //宣告初始值init var init = parseFloat(getCSS(obj,attr)); //如果初始值等於目標點,則返回 if(init == target) return; var fn = json.fn; //聲明當前設定值 var set = 0; //宣告衰減係數 var k = 1; //如果沒有建立定時器物件,則在obj下建立定時器物件 if(!obj.timers){obj.timers = {};} //清除定時器 if(obj.timers[attr]){return;} //開啟定時器 obj.timers[attr] = setInterval(function(){ //更新當前值 set = init + target + len*k; k*=-0.5; obj.style[attr] = init + set + 'px'; //當元素到達目標點時,停止定時器 if(Math.round(len*k) == 0){ obj.style[attr] = target + 'px'; clearInterval(obj.timers[attr]); obj.timers[attr] = 0; fn && fn.call(obj); } },50); } reset.onclick = function(){history.go();} btn.onclick = function(){ elasticMove({obj:test,target:500}) } </script> </body> </html>
步長分析
彈性運動的另一種方法是使用步長分析法
步長分析法是分析定時器每一次執行時樣式的變化值。我們可以將彈性運動分解為受彈力影響的運動和受阻力影響的運動
由於受到彈力的影響,所以元素距離目標點越遠,速度越大;由於受到阻力的影響,所以元素每次運動都會有速度損耗
//宣告彈性距離 var len; //宣告彈性係數 var k; //宣告損耗係數 var z; //獲取當前樣式值cur cur = parseFloat(getCSS(obj,attr)); //更新彈性距離 len = target - cur; //彈力影響 step += len*k; //阻力影響 step = step*z; obj.style[attr] = cur + step + 'px'; //當元素的步長值接近於0,並且彈性距離接近於0時,停止定時器 if(Math.round(step) == 0 && Math.round(len) == 0){ obj.style[attr] = target + 'px'; clearInterval(obj.timers[attr]); obj.timers[attr] = 0; fn && fn.call(obj); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <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:300px;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function elasticMove(json){ var obj = json.obj; var attr = json.attr; //樣式預設值為'left' attr = attr || 'left'; var target = json.target; //目標點預設值為'200' target = Number(target) || 200; var fn = json.fn; //宣告步長值 var step = 0; //宣告彈性距離 var len = target; //宣告彈性係數 var k=json.k; //預設值為0.7 k = Number(k) || 0.7; //宣告損耗係數 var z=json.z; //預設值為0.7 z = Number(z) || 0.7; //聲明當前值 var cur = parseFloat(getCSS(obj,attr)); //如果沒有建立定時器物件,則在obj下建立定時器物件 if(!obj.timers){obj.timers = {};} //清除定時器 if(obj.timers[attr]){return;} //開啟定時器 obj.timers[attr] = setInterval(function(){ //獲取當前樣式值cur cur = parseFloat(getCSS(obj,attr)); //更新彈性距離 len = target - cur; //彈力影響 step += len*k; //阻力影響 step = step*z; obj.style[attr] = cur + step + 'px'; //當元素的步長值接近於0,並且彈性距離接近於0時,停止定時器 if(Math.round(step) == 0 && Math.round(len) == 0){ obj.style[attr] = target + 'px'; clearInterval(obj.timers[attr]); obj.timers[attr] = 0; fn && fn.call(obj); } },20); } reset.onclick = function(){history.go();} btn.onclick = function(){ elasticMove({obj:test,target:300}) } </script> </body> </html>
彈性過界
IE8-瀏覽器存在彈性過界問題,當寬度width或高度height等不能出現負值的樣式出現負值時將會報錯。所以,需要判斷樣式為高度或寬度時,樣式值小於0時,等於0
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <button id="btn">彈性運動</button> <button id="reset">還原</button> <div id="test" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div> <script> function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function elasticMove(json){ var obj = json.obj; var attr = json.attr; //樣式預設值為'left' attr = attr || 'left'; var target = json.target; //目標點預設值為200 if(isNaN(Number(target))){ target = 200; }else{ target = Number(target); } var fn = json.fn; //宣告步長值 var step = 0; //宣告彈性距離 var len = target; //宣告彈性係數 var k=json.k; //預設值為0.7 k = Number(k) || 0.7; //宣告損耗係數 var z=json.z; //預設值為0.7 z = Number(z) || 0.7; //聲明