1. 程式人生 > >javascript運動系列第五篇——緩衝運動和彈性運動

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;
    //聲明