互動動畫的一個主要目標是創造出流暢的使用者體驗,其中大多數的使用者互動都是透過滑鼠和觸控螢幕實現的。
在這篇文章中,我想分享一些JS對於物體移動的常見用法,包括拖曳和投擲效果。
可以將一個滑鼠點選事件分解成兩個事件:滑鼠按下事件和按鍵彈起事件。通常情況下這兩個事件是同時發生的。不過,有時滑鼠按下後,滑鼠還會移動一段時間才彈起,這種操作稱為拖曳,即按下、移動、在釋放。
在canvas動畫中,滑鼠事件只能被HTML DOM樹上的canvas元素所捕獲,因此,我們需要手動計算滑鼠事件在canvas上的發生位置,並判斷它是否發生在哪個繪製到canvas的物體上。需要注意的滑鼠事件有:mousedown、mousemove和mouseup。具體細節可參考我的相關部落格文章《JavaScript動畫詳解(一) —— 迴圈與事件監聽》。
隨著觸控螢幕裝置的流行,我們很可能需要在動畫中捕捉使用者的觸控事件。雖然觸控螢幕與滑鼠是不同的設備,但幸運的是,在DOM樹上捕捉觸控事件與捕捉滑鼠事件的差異不大。
與滑鼠事件mousedown、mousemove和mouseup相對應的觸控事件分別是touchstart、touchend與touchmove。
使用手指與滑鼠的一個比較大的區別在於,滑鼠總是出現在螢幕上,而手指卻不是一直處於觸控狀態。常見的做法是,引入自訂屬性isPressed,用來告訴我們螢幕上是否有手指在觸摸。具體細節可參考我的相關部落格文章《JavaScript動畫詳解(一) —— 迴圈與事件監聽》。
拖曳事件包含了三個子事件:滑鼠按下、移動、釋放。透過不斷更新物體的座標位置使其追隨滑鼠指標的位置,就可以實現在canvas元素上拖曳物體。 另外還需要一個自訂屬性isPressed來標示目前滑鼠是否按下,預設為false表示滑鼠為彈起狀態。實作程式碼包含以下過程:
1 . 捕捉mousedown事件,判斷目前滑鼠是否在物體內。當滑鼠在物體內按下時,設定isPressed = true;
2 . 捕捉mousemove事件,在處理程序內判斷當isPressed = true時,透過不斷更新物件的座標位置使其追隨滑鼠指標的位置來模擬出滑鼠拖曳效果;
3 . 捕捉mouseup事件,將isPressed設定為false;
HTML程式碼如下:
<canvas id="canvas" width="400" height="400"></canvas>
# JavaScript程式碼如下:
// 创建画球函数 function Ball() { this.x = 0; this.y = 0; this.radius = 20; this.fillStyle = "#f85455"; this.draw = function(cxt) { cxt.fillStyle = this.fillStyle; cxt.beginPath(); cxt.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, true); cxt.closePath(); cxt.fill(); } } // 获得当前鼠标位置 function getMouse(ev) { var mouse = { x: 0, y: 0 }; var event = ev || window.event; if(event.pageX || event.pageY) { x = event.x; y = event.y; }else { var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; x = event.clientX + scrollLeft; y = event.clientY + scrollTop; } mouse.x = x; mouse.y = y; return mouse; } var canvas = document.getElementById("canvas"), context = canvas.getContext("2d"), ball = new Ball(), mouse = {x: 0, y: 0}, isPressed = false; ball.x = 20; ball.y = 20; // 渲染小球 ball.draw(context); // 小球拖拽事件 canvas.addEventListener("mousedown", mouseDown, false); canvas.addEventListener("mousemove", mouseMove, false); canvas.addEventListener("mouseup", mouseUp, false); function mouseDown(ev) { // 判断当前鼠标是否在小球内 mouse = getMouse(ev); if(Math.pow(mouse.x - ball.x, 2) + Math.pow(mouse.y - ball.y, 2) <= Math.pow(ball.radius, 2)) { isPressed = true; } } function mouseMove(ev) { if(isPressed) { mouse = getMouse(ev); ball.x = mouse.x; ball.y = mouse.y; context.clearRect(0, 0, canvas.width, canvas.height); ball.draw(context); } } function mouseUp(ev) { // 标示鼠标弹起事件 isPressed = false; }
但是,這個範例是有bug的!很快你就能發現,在拖曳的時候,小球的中心位置都在滑鼠位置上,特別是當滑鼠點擊小球邊緣時,會看見小球的中心點突然就跳動到了滑鼠遊標的位置上了。顯然,這顯得有點唐突。
我們可以稍作改良:
在滑鼠按下的時候記錄目前滑鼠位置與小球中心點位置的偏移量;
// 记录鼠标按下时,鼠标与小球圆心的偏移量 dx = mouse.x - ball.x; dy = mouse.y - ball.y;
在滑鼠移動時,用滑鼠的目前位置減去滑鼠按下時記錄的偏移量
ball.x = mouse.x - dx; ball.y = mouse.y - dy;
在動畫中如何表現投擲呢?用滑鼠選取一個物體,拖曳著它向某個方向移動,放開滑鼠後,物體沿著拖曳的方向繼續移動。
在投擲物體時,必須在拖曳物體的過程中計算物體的速度向量,並在釋放物體時將這個速度向量賦給物體。實際上,計算拖曳時物體的速度向量的過程,恰好 與對物體應用速度向量的過程相反。在對物體套用速度向量時,將速度追加到物體原來所在的位置上,從而計算出物體的新位置,這個公式可以寫成:舊的位置+ 速度向量= 新的位置,即速度向量= 新的位置– 舊的位置。
為了實現投擲行為,需要對前面的程式碼做一些改動。首先,檢查滑鼠是否按下,如果按下,用oldX和oldY變數儲存小球舊的x、y座標位置,並更新小球的拖曳速度。
特定JavaScript程式碼實作如下:
// 创建画球函数 function Ball() { this.x = 0; this.y = 0; this.radius = 20; this.fillStyle = "#f85455"; this.draw = function(cxt) { cxt.fillStyle = this.fillStyle; cxt.beginPath(); cxt.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, true); cxt.closePath(); cxt.fill(); } } // 获得当前鼠标位置 function getMouse(ev) { var mouse = { x: 0, y: 0 }; var event = ev || window.event; if(event.pageX || event.pageY) { x = event.x; y = event.y; }else { var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; x = event.clientX + scrollLeft; y = event.clientY + scrollTop; } mouse.x = x; mouse.y = y; return mouse; } var canvas = document.getElementById("canvas"), context = canvas.getContext("2d"), ball = new Ball(), mouse = {x: 0, y: 0}, isPressed = false, oldX = 0, oldY = 0, currentX = 0, currentY = 0, vx = 0, vy = 0; ball.x = 200; ball.y = 200; // 声明鼠标按下时,鼠标与小球圆心的距离 var dx = 0, dy = 0; // 渲染小球 ball.draw(context); // 小球拖拽事件 canvas.addEventListener("mousedown", mouseDown, false); canvas.addEventListener("mousemove", mouseMove, false); canvas.addEventListener("mouseup", mouseUp, false); function mouseDown(ev) { // 判断当前鼠标是否在小球内 mouse = getMouse(ev); if(Math.pow(mouse.x - ball.x, 2) + Math.pow(mouse.y - ball.y, 2) <= Math.pow(ball.radius, 2)) { isPressed = true; // 记录鼠标按下时,鼠标与小球圆心的距离 dx = mouse.x - ball.x; dy = mouse.y - ball.y; // 获得小球拖拽前的位置 mouse = getMouse(ev); oldX = mouse.x; oldY = mouse.y; } } function mouseMove(ev) { if(isPressed) { mouse = getMouse(ev); ball.x = mouse.x - dx; ball.y = mouse.y - dy; context.clearRect(0, 0, canvas.width, canvas.height); ball.draw(context); } } function mouseUp(ev) { // 标示鼠标弹起事件 isPressed = false; // 把鼠标与圆心的距离位置恢复初始值 dx = 0; dy = 0; // 获得小球拖拽后的位置 mouse = getMouse(ev); currentX = mouse.x; currentY = mouse.y; // 更新速度向量:速度向量 = 新的位置 - 旧的位置 vx = (currentX - oldX) * 0.05; vy = (currentY - oldY) * 0.05; drawFrame(); } // 缓动动画 function drawFrame() { animRequest = window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); if(ball.x >= canvas.width - 30 || ball.x <= 30 || ball.y >= canvas.height - 30 || ball.y <= 30) { window.cancelAnimationFrame(animRequest); } ball.x += vx; ball.y += vy; ball.draw(context); }
這個Demo的邊界判斷還有一些bug,過些日子再修復。
物體移動事件可以有很多總運動形式,但都可以分解為三個單獨的事件來控制:按下、移動、釋放,在滑鼠事件中分別對應的是mousedown、 mousemove和mouseup,在觸控事件中分別對應的是touchstart、touchmove和touchend。透過不斷更新物體的座標位 置使其追隨滑鼠指標的位置,就可以實現在canvas元素上拖曳和投擲的效果。
以上是透過JavaScript實現移動物體程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!