前面的話
緩沖運動指的是減速運動,減速到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>
文章列表