文章出處

前面的話

  緩沖運動指的是減速運動,減速到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

  把彈性運動封裝成elasticMove.js

<!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;
    //聲明當前值
    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;
        //防止彈性過界
        if((attr == 'height' || attr == 'width') && (cur + step) < 0){
            obj.style[attr] = 0;
        }else{
            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(){
  easyMove({obj:test,attr:'width',target:20})
}
</script>    
</body>
</html>

彈性菜單

  下面利用封裝的elasticMove.js來實現一個彈性菜單的應用

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<base href="http://www.cnblogs.com/" target="_blank">
<style>
#nav{
    list-style:none;
    padding: 0;
    margin: 0 50px 0;
    text-align:center;
    color:white;
    font-weight:bold;
    background-color: #25517A;
    cursor:pointer;
    overflow:hidden;
    width: 500px;
}    
.navItem{
    line-height: 30px;
    float:left;
    width:100px;
    text-decoration: none;
    color:inherit;
}
#navActive{
    width: 100px;
    height: 30px;
    background-color: rgba(0,0,0,0.3);
    position:absolute;
    margin-top: -30px;
    cursor:pointer;
}
</style>
</head>
<body>
<nav id="nav">
    <a class="navItem" href="/">首頁</a>
    <a class="navItem" href="/pick/">精華</a>
    <a class="navItem" href="/candidate/">候選</a>
    <a class="navItem" href="/news/">新聞</a>
    <a class="navItem" href="/following">關注</a>
</nav>
<div id="navActive"></div>
<script src="http://files.cnblogs.com/files/xiaohuochai/elasticMove.js"></script>
<script>
//navActive默認處于導航欄最左側
navActive.style.left = nav.offsetLeft + 'px';
navActive.target =    nav.getElementsByTagName('a')[0];
nav.onmousemove = function(e){
    e = e || event;
    navActive.target = e.target || e.srcElement;
    elasticMove({obj:navActive,attr:'left',target:navActive.target.offsetLeft})
}
//點擊navActive觸發其所在位置的點擊事件
navActive.onclick = function(e){
    navActive.target.click();
}
</script>
</body>
</html>

彈性拖拽

  彈性運動的另一個常見應用是彈性拖拽效果。例如,iphone手機上滑屏時的緩動效果就是利用彈性拖拽實現的

  下面利用封裝的elasticMove.js來實現一個彈性拖拽的應用

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#box{
    width: 200px;
    height: 200px;
    border: 2px solid black;
    margin: 50px 0 0 100px;
    cursor:pointer;
    position:relative;
    overflow:hidden;
}
#list{
    width: 1000px;
    height: 200px;
    margin: 0;
    padding: 0;
    list-style:none;
    text-align: center;
    font-size:30px;
    position:absolute;
    left:0;
}
.listItem{
    float: left;
    width: 200px;
    line-height: 200px;
}
</style>
</head>
<body>
<div id="box">
    <ul id="list" class="clear">
        <li class="listItem" style="background-color:lightblue">第一屏</li>
        <li class="listItem" style="background-color:lightgreen">第二屏</li>
        <li class="listItem" style="background-color:lightseagreen">第三屏</li>
        <li class="listItem" style="background-color:lightgrey">第四屏</li>
        <li class="listItem" style="background-color:lightcoral">第五屏</li>
    </ul>    
</div>
<script src="http://files.cnblogs.com/files/xiaohuochai/elasticMove.js"></script>
<script>
//表示當前屏幕處于第幾屏,默認為第0屏
var index = 0;
//獲取屏幕的寬度
var baseWidth = parseFloat(getCSS(box,'width'));
list.onmousedown = function(e){
    e = e || event;
     //獲取元素距離定位父級的x軸及y軸距離
    var x0 = this.offsetLeft;
    //獲取此時鼠標距離視口左上角的x軸距離
    var x1 = e.clientX;
    //如果拖拽時,彈性運動還沒有走完,則拖拽操作無效
    if(list.timers){
        if(list.timers.left){
            return;
        }
    }
    document.onmousemove = function(e){
        e = e || event;
        //獲取此時鼠標距離視口左上角的x軸距離
        var x2 = e.clientX;  
        //計算此時元素應該距離視口左上角的x軸距離
        var X = x0 + x2 - x1;
        //將X的值賦給left,使元素移動到相應位置
        list.style.left = X + 'px';
    }
    document.onmouseup = function(e){
        //當鼠標抬起時,拖拽結束,則將onmousemove賦值為null即可
        document.onmousemove = null;
        //釋放全局捕獲
        if(list.releaseCapture){
            list.releaseCapture();
        }
        e =  e || event;
        var x3 = e.clientX;
        //向右滑動
        if(x3 > x1){
            index--;
            if(index < 0){
                index = 0;
            }
            elasticMove({
                obj:list,
                target:-1*baseWidth*index
            })  
        }
        //向左滑動
        if(x3 < x1)
        {
            index++;
            if(index > 4){
                index = 4;
            }
            elasticMove({
                obj:list,
                target:-1*baseWidth*index
            })
        }
    }
    //阻止默認行為
    return false;
    //IE8-瀏覽器阻止默認行為
    if(list.setCapture){
        list.setCapture();
    }
}
//防止無關文字被選中
document.onmousedown = function(){
    return false;
}
</script>    
</body>
</html>


文章列表


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

    IT工程師數位筆記本

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