前面的話
碰撞運動可能是運動系列里面比較復雜的運動了。碰撞可以分為碰壁和互碰兩種形式,而碰撞前后的運動形式也可以分為變速和勻速兩種,如果再涉及到阻力,具有速度損耗的話,就更加復雜了。本文先介紹碰壁運動
勻速碰壁
碰壁是一種常見的碰撞形式,勻速碰壁是最簡單的碰撞運動
假設一個密閉空間內一個彈性小球,小球有一個隨機方向隨機大小的初始速度。當小球碰壁時,速度不損失,而是做反向的勻速運動
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test" style="height: 100px;width: 100px;background:lightblue;position:absolute;top:60px;left:20px;border-radius:50%;"></div> <button id="btn1">開始運動</button> <button id="btn2">停止運動</button> <span>游戲說明:當小球開始運動后,點擊小球一次得一分</span> <div id="result"></div> <script> var timer,i=0; //聲明得分 var key = 0; var arr = ['orange','lightgreen','lightcoyal','pink','lightcyan','lightgray','lightseagreen','lightsteelblue']; function changeColor(){ i++; if(i == arr.length){ i = 0; } test.style.background = arr[i]; } document.onmousemove = function(){ return false; } test.onclick = function(){ //當小球開始運動后,開始記分 if(test.timer){ result.innerHTML = '當前得分為:' + ++key + '分' } changeColor(); } btn1.onclick = function(){ result.innerHTML = '' //將分數清零 key = 0; collisionMove({ obj:test }) clearInterval(timer); timer = setInterval(function(){ changeColor(); },500); } btn2.onclick = function(){ clearInterval(timer); clearInterval(test.timer); test.timer = 0; result.innerHTML = '你得到:' + key + '分,再接再厲!' } function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function collisionMove(json){ var obj = json.obj; var fn = json.fn; //聲明x、y軸的當前值 var curX = parseFloat(getCSS(obj,'left')); var curY = parseFloat(getCSS(obj,'top')); //聲明x、y軸的步長值 var stepX = json.stepX; var stepY = json.stepY; //步長值默認值為[-25,-20,-15,-10,-5,0,5,10,15,20]中的一個隨機數 stepX = Number(stepX) || 5*Math.floor(Math.random() * 10 - 5); stepY = Number(stepY) || 5*Math.floor(Math.random() * 10 - 5); //聲明x、y軸方向 var dirX = json.dirX; var dirY = json.dirY; dirX = stepX > 0 ? '+' : '-'; dirY = stepY > 0 ? '+' : '-'; //聲明offset寬高 var offsetWidth = obj.offsetWidth; var offsetHeight = obj.offsetHeight; //聲明元素活動區域寬高 var activeWidth = json.activeWidth; var activeHeight = json.activeHeight; //元素獲取區域寬高默認值為可視區域寬高 activeWidth = Number(activeWidth) || document.documentElement.clientWidth; activeHeight = Number(activeHeight) || document.documentElement.clientHeight; //聲明left、top樣式值 var left; var top; //清除定時器 if(obj.timer){return;} //開啟定時器 obj.timer = setInterval(function(){ //獲取x、y軸的當前值 curX = parseFloat(getCSS(obj,'left')); curY = parseFloat(getCSS(obj,'top')); //更新left、top值 left = curX + stepX; top = curY + stepY; //右側碰壁前一刻,步長大於剩余距離,且元素向右運動時 if((left > activeWidth - offsetWidth) && (dirX == '+')){ left = activeWidth - offsetWidth; } //左側碰壁前一刻,步長大於剩余距離,且元素向左運動時 if((Math.abs(stepX) > curX) && (dirX == '-')){ left = curX; } //下側碰壁前一刻,步長大於剩余距離,且元素向下運動時 if((top > activeHeight - offsetHeight) && (dirY == '+')){ top = activeHeight - offsetHeight; } //上側碰壁前一刻,步長大於剩余距離,且元素向上運動時 if((Math.abs(stepY) > curY) && (dirY == '-')){ top = curY; } obj.style.left= left + 'px'; obj.style.top = top + 'px'; //左側或右側碰撞瞬間 if(left == activeWidth - offsetWidth || left == curX){ stepX = -stepX; } //上側或下側碰撞瞬間 if(top == activeHeight - offsetHeight || top == curY){ stepY = -stepY; } //更新運動方向 dirX = stepX > 0 ? '+' : '-'; dirY = stepY > 0 ? '+' : '-'; },20); } </script> </body> </html>
自由落體
元素在實際運動中,並不是保持勻速運動的,更多的是變速運動,而且會有速度損耗。典型的場景是自由落體運動,物體落地之后會反方向彈幾下,最終停在地上
自由落體運動可以看做是重力與阻力合作的結果。在空中運動時,向下運動時,做勻加速運動;向上運動時,做勻減速運動。與地面碰撞的瞬間,產生速度損耗
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test" style="height:50px;width: 50px;background:lightblue;position:absolute;top:60px;border-radius:50%;"></div> <button id="btn1">開始運動</button> <button id="btn2">還原</button> <script> document.onmousedown = function(){ return false; } btn1.onclick = function(){ collisionMove({ obj:test }) } btn2.onclick = function(){ history.go(); } function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function collisionMove(json){ var obj = json.obj; var fn = json.fn; //聲明y軸的當前值 var curY = parseFloat(getCSS(obj,'top')); //聲明offset高 var offsetHeight = obj.offsetHeight; //聲明元素活動區域高 var activeHeight = json.activeHeight; //元素獲取區域寬高默認值為可視區域高 activeHeight = Number(activeHeight) || document.documentElement.clientHeight; //聲明y軸的步長值 var stepY = 0; //聲明top樣式值 var top; //聲明減速系數 var k = 0.8; //聲明碰撞次數 var i = 0; //清除定時器 if(obj.timer){return;} //開啟定時器 obj.timer = setInterval(function(){ //獲取y軸的當前值 curY = parseFloat(getCSS(obj,'top')); //更新步長值stepY stepY+= 5; //更新top值 top = curY + stepY; //下側碰壁前一刻,步長大於剩余距離,且元素向下運動時 if(top > activeHeight - offsetHeight){ top = activeHeight - offsetHeight; } obj.style.top = top + 'px'; //下側碰撞瞬間,改變運動方向,並產生速度損耗 if(top == activeHeight - offsetHeight){ //若碰撞10次后,則停止運動 i++; if(i== 10){ clearInterval(obj.timer) obj.timer = 0; fn && fn.call(obj); } stepY = -stepY * k; } },20); } </script> </body> </html>
投擲碰壁
如果一個物體向空中投擲出去,會呈現一個拋物線的效果,最終經過與地面碰撞多次后停止
投擲碰撞效果是x軸和y軸的合效果。x軸做勻速運動,當物體碰到地面后,x軸速度發生損耗;y軸做勻加速運動,當物體碰到地面后,y軸速度同樣發生損耗
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test" style="height: 50px;width: 50px;background:lightblue;position:absolute;top:200px;left:20px;border-radius:50%;"></div> <button id="btn1">開始運動</button> <button id="btn2">還原</button> <script> document.onmousedown = function(){ return false; } btn1.onclick = function(){ collisionMove({ obj:test }) } btn2.onclick = function(){ history.go(); } function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } function collisionMove(json){ var obj = json.obj; var fn = json.fn; //聲明y軸的當前值 var curY = parseFloat(getCSS(obj,'top')); //聲明offset高 var offsetHeight = obj.offsetHeight; //聲明元素活動區域高 var activeHeight = json.activeHeight; //元素獲取區域寬高默認值為可視區域高 activeHeight = Number(activeHeight) || document.documentElement.clientHeight; //聲明x、y軸的步長值 var stepY = -50; var stepX = 10; //聲明top、left樣式值 var top; var left; //聲明減速系數 var k = 0.8; //聲明碰撞次數 var i = 0; //清除定時器 if(obj.timer){return;} //開啟定時器 obj.timer = setInterval(function(){ //獲取x、y軸的當前值 curX = parseFloat(getCSS(obj,'left')); curY = parseFloat(getCSS(obj,'top')); //更新步長值stepY stepY+= 5; //更新top、left值 top = curY + stepY; left = curX + stepX; //下側碰壁前一刻,步長大於剩余距離,且元素向下運動時 if(top > activeHeight - offsetHeight){ top = activeHeight - offsetHeight; } obj.style.top = top + 'px'; obj.style.left = left + 'px'; //下側碰撞瞬間,改變運動方向,並產生速度損耗 if(top == activeHeight - offsetHeight){ //若碰撞10次后,則停止運動 i++; if(i== 10){ clearInterval(obj.timer) obj.timer = 0; fn && fn.call(obj); } //速度損耗 stepY = -stepY * k; stepX = stepX * k; } },20); } </script> </body> </html>
拖拽碰壁
實際情況下,一個物體默認具有重力效果。物體的重力效果是時時刻刻都在發生的,相當於定時器的每次運動,都有向下的勻加速運動
如果投擲速度不同,則運動速度也不相同。在碰壁的情況下,速度會有損耗,並且發生速度方向變化。最終,物體會落到地上
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test" style="height: 100px;width: 100px;border-radius:50%;background:pink;position:absolute;top:40px;left:0;"></div> <script> //聲明元素投擲步長值 var stepX=0,stepY=0; //默認情況下,也存在重力效果 collisionMove({ obj:test, stepX:stepX, stepY:stepY }) function getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } //碰撞運動函數 function collisionMove(json){ var obj = json.obj; var fn = json.fn; //聲明x、y軸的當前值 var curX = parseFloat(getCSS(obj,'left')); var curY = parseFloat(getCSS(obj,'top')); //聲明x、y軸的步長值 var stepX = json.stepX; var stepY = json.stepY; //聲明元素的重要加速度 var g = json.g || 3; //步長值默認值為10 if(isNaN(Number(stepX))){ stepX = 10; }else{ stepX = Number(stepX); } if(isNaN(Number(stepY))){ stepY = 10; }else{ stepY = Number(stepY); } //聲明x、y軸方向 var dirX = json.dirX; var dirY = json.dirY; dirX = stepX > 0 ? '+' : '-'; dirY = stepY > 0 ? '+' : '-'; //聲明offset寬高 var offsetWidth = obj.offsetWidth; var offsetHeight = obj.offsetHeight; //聲明元素活動區域寬高 var activeWidth = json.activeWidth; var activeHeight = json.activeHeight; //元素獲取區域寬高默認值為可視區域寬高 activeWidth = Number(activeWidth) || document.documentElement.clientWidth; activeHeight = Number(activeHeight) || document.documentElement.clientHeight; //聲明left、top樣式值 var left; var top; //聲明減速系數 var k = 0.8; //聲明碰撞次數 var i = 0; //清除定時器 if(obj.timer){return;} //開啟定時器 obj.timer = setInterval(function(){ //獲取x、y軸的當前值 curX = parseFloat(getCSS(obj,'left')); curY = parseFloat(getCSS(obj,'top')); //受到重力影響,更新步長值stepY stepY += g; //更新left、top值 left = curX + stepX; top = curY + stepY; //右側碰壁前一刻,步長大於剩余距離,且元素向右運動時 if((left > activeWidth - offsetWidth) && (dirX == '+')){ left = activeWidth - offsetWidth; } //左側碰壁前一刻,步長大於剩余距離,且元素向左運動時 if((Math.abs(stepX) > curX) && (dirX == '-')){ left = curX; } //下側碰壁前一刻,步長大於剩余距離,且元素向下運動時 if((top > activeHeight - offsetHeight) && (dirY == '+')){ top = activeHeight - offsetHeight; } //上側碰壁前一刻,步長大於剩余距離,且元素向上運動時 if((Math.abs(stepY) > curY) && (dirY == '-')){ top = curY; } obj.style.left= left + 'px'; obj.style.top = top + 'px'; //左側或右側碰撞瞬間 if(left == activeWidth - offsetWidth || left == curX){ //x軸方向速度損耗,並發生方向變化 stepX = -stepX * k; } //上側或下側碰撞瞬間 if(top == activeHeight - offsetHeight || top == curY){ //y軸方向速度損耗,並發生方向變化 stepY = -stepY * k; //x軸方向速度損耗 stepX = stepX * k; } //元素與地面碰撞10次后,則停止運動 if(top == activeHeight - offsetHeight){ i++; if(i== 10){ clearInterval(obj.timer) obj.timer = 0; fn && fn.call(obj); } } //更新運動方向 dirX = stepX > 0 ? '+' : '-'; dirY = stepY > 0 ? '+' : '-'; },20); } //初始拋擲 test.onmousedown = function(e){ e = e || event; //聲明上一次mousemove事件的坐標位置 var lastX2 = e.clientX; var lastY2 = e.clientY; //獲取元素距離定位父級的x軸及y軸距離 var x0 = this.offsetLeft; var y0 = this.offsetTop; //獲取此時鼠標距離視口左上角的x軸及y軸距離 var x1 = e.clientX; var y1 = e.clientY; //鼠標按下時,獲得此時的頁面區域 var L0 = 0; var R0 = document.documentElement.clientWidth; var T0 = 0; var B0 = document.documentElement.clientHeight; //鼠標按下時,獲得此時的元素寬高 var EH = this.offsetHeight; var EW = this.offsetWidth; document.onmousemove = function(e){ e = e || event; //獲取此時鼠標距離視口左上角的x軸及y軸距離 var x2 = e.clientX; var y2 = e.clientY; stepX = x2 - lastX2; stepY = y2 - lastY2; lastX2 = e.clientX; lastY2 = e.clientY; //計算此時元素應該距離視口左上角的x軸及y軸距離 var X = x0 + (x2 - x1); var Y = y0 + (y2 - y1); /******范圍限定*******/ //獲取鼠標移動時元素四邊的瞬時值 var L = X; var R = X + EW; var T = Y; var B = Y + EH; //在將X和Y賦值給left和top之前,進行范圍限定 //只有在范圍內時,才進行相應的移動 //如果脫離左側范圍,則left置L0 if(L < L0){X = L0;} //如果脫離右側范圍,則left置為R0 if(R > R0){X = R0 - EW;} //如果脫離上側范圍,則top置T0 if(T < T0){Y = T0;} //如果脫離下側范圍,則top置為B0 if(B > B0){Y = B0 - EH;} //將X和Y的值賦給left和top,使元素移動到相應位置 test.style.left = X + 'px'; test.style.top = Y + 'px'; } document.onmouseup = function(e){ e = e || event; var maxHeight = document.documentElement.clientHeight - test.offsetHeight; var maxWidth = document.documentElement.clientWidth - test.offsetWidth; //以設置的投擲速度來進行碰撞運動 collisionMove({ obj:test, stepX:stepX, stepY:stepY }) //當鼠標抬起時,拖拽結束,則將onmousemove賦值為null即可 document.onmousemove = null; //釋放全局捕獲 if(test.releaseCapture){ test.releaseCapture(); } } //阻止默認行為 return false; //IE8-瀏覽器阻止默認行為 if(test.setCapture){ test.setCapture(); } } </script> </body> </html>