文章出處

前面的話

  前面介紹過勻速運動的實現及注意事項,本文在勻速運動的基礎上,更進一步,實現各種變速運動,包括加速運動、減速運動、緩沖運動、重力運動和彈性運動

 

準備工作

勻速運動

  在原生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>


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()