canvas動畫:自由落體運動


經過前面的文章,我們已經能夠在canvas畫布上畫出各種炫酷的圖形和畫面,但是這些畫面都是禁止的,怎么樣才能讓他們動起來呢?

動畫的基本步驟

我們知道,動畫是一幀一幀的畫面不斷反映實現的,人的眼睛看到一幅畫或一個物體后,在0.34秒內不會消失。利用這一原理,在一幅畫還沒有消失前播放下一幅畫,就會給人造成一種流暢的視覺變化效果。在canvas中,就是在繪制完當前畫面之后,快速的繪制下一個畫面。步驟如下:

  • 清空canvas。
    • 除非接下來要畫的內容會完全充滿 canvas (例如背景圖),否則你需要清空所有畫布上的內容。最簡單的做法就是用clearRect方法。
  • 保存canvas狀態。
    • 如果你要改變一些會改變 canvas 狀態的設置(樣式,變形之類的),又要在每畫一幀之時都是原始狀態的話,你需要先保存一下。
  • 繪制動畫圖形(animated shapes)。
    • 這一步才是重繪動畫幀。
  • 恢復 canvas 狀態。
    • 如果已經保存了 canvas 的狀態,可以先恢復它,然后重繪下一幀。

操縱動畫

在 canvas 上繪制內容是用 canvas 提供的或者自定義的方法,而通常,我們僅僅在腳本執行結束后才能看見結果,比如說,在 for 循環里面做完成動畫是不太可能的。

因此,為了實現動畫,我們需要一些可以定時執行重繪的方法。window對象提供了下面的方法實現定時動畫:

  • setInterval(function, delay)當設定好間隔時間后,function會定期執行
  • setTimeout(function, delay)在設定好的時間之后執行函數
  • requestAnimationFrame(callback)告訴瀏覽器你希望執行一個動畫,並在重繪之前,請求瀏覽器執行一個特定的函數來更新動畫。

如果你並不需要與用戶互動,你可以使用setInterval()方法,它就可以定期執行指定代碼。如果我們需要做一個游戲,我們可以使用鍵盤或者鼠標事件配合上setTimeout()方法來實現。通過設置事件監聽,我們可以捕捉用戶的交互,並執行相應的動作。

window.requestAnimationFrame()這個方法提供了更加平緩並更加有效率的方式來執行動畫,當系統准備好了重繪條件的時候,才調用繪制動畫幀。一般每秒鍾回調函數執行60次,也有可能會被降低。

在使用window.requestAnimationFrame()方法的過程中,我推薦使用下面的兼容性方法來代替:

window.requestAnimationFrame = (function(){
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function (callback) {
                window.setTimeout(callback, 1000 / 60);
            };
})();

canvas動畫實例-模擬小球自由落體運動

上面介紹了canvas動畫的基本概念,接下來我們將會在canvas中實現小球下落的動畫。小球的完整代碼再本文結尾。點擊可跳轉到結尾

繪制小球

首先需要在canvas上繪制一個小球。

var ctx = document.getElementById('canvas').getContext('2d');
if (!ctx) {
    console.log('您的瀏覽器不支持canvas');
    // 可以拋出異常強制結束JS執行
    throw new Error("Do not support canvas");
}

var ball = {
    x: 100,  // 小球的x坐標
    y: 100,  // 小球的y坐標
    radius: 25,  // 小球半徑
    color: 'cyan', // 小球顏色
    draw: function() {  // 繪制小球的函數
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fillStyle = this.color;
        ctx.fill();
    },
    clear: function() {  // 清除小球區域的函數
        ctx.clearRect(this.x - this.radius,
                this.y - this.radius,
                this.radius * 2,
                this.radius * 2);
    }
}
ball.draw(); // 繪制小球

添加運動描述

繪制了小球之后,要添加動畫,還需要為小球添加速率矢量進行移動。另外,速度也是變化的量,對於只有落體運動,還有豎直方向的重力加速度,所以還需要為小球加上加速度。

var ball = {
        x: 100,  // 小球的x坐標
        y: 100,  // 小球的y坐標
        vx: 0,   // 小球水平方向速度
        vy: 0,   // 小球豎直方向速度
        ax: 0,   // 小球水平方向加速度
        ay: 0,   // 小球豎直方向加速度
        dt: 1,   // 兩幀之間的時間為1個單位時間
        radius: 25,  // 小球半徑
        color: 'cyan', // 小球顏色
        s: function(v, a, t) {
            // 勻加速直線運動的位移公式:s=vt+1/2at^2
            return v * t + (1 / 2.0) * a * t * t;
        },
        dx: function() {
            // 計算水平方向的位移
            return this.s(this.vx, this.ax, this.dt);
        },
        dy: function() {
            // 計算豎直方向的位置
            return this.s(this.vy, this.ay, this.dt);
        },
        next: function() {
            // 計算小球下一時刻的位移
            this.x += this.dx();
            this.y += this.dy();

            // 計算小球下一時刻的速度:v_t = v_0 + a*t
            this.vx = this.vx + this.ax * this.dt;
            this.vy = this.vy + this.ay * this.dt;

            this.boundary(0, canvas.width, canvas.height, 0);
        },
    };

假設每一幀之間的時間是單位時間,那么根據當前小球的位置速度和加速度,我們就可以計算下一幀的小球的位置和速度,此時清空上一幀的canvas,再繪制下一幀,即可實現動畫效果。

var animate; // 記錄動畫
ball.draw();

// 繪制一幀
function draw() {
    // 1:清空畫布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // 2:繪制小球
    ball.draw();
    // 3:計算小球的下一個狀態
    ball.next();
    // 4:進入下一幀
    animate = window.requestAnimationFrame(draw);
}

邊界處理

若沒有任何的碰撞檢測,我們的小球很快就會超出畫布。我們需要檢查小球的 x 和 y 位置是否已經超出畫布的尺寸以及是否需要將速度矢量反轉。

boundary: function(top, right, bottom, left) {
    // 檢測小球下一幀是否出界,出界則補正
    if (this.y > bottom) {  // 下邊界越界
        this.vy = -this.vy;  // 速度反向
    } else if (this.y < top) {
        this.vy = -this.vy;
    } else if (ball.x > right) {
        this.vx = -this.vx;  // 速度反向
    } else if (ball.x < left) {
        this.vx = -this.vx;
    }

}

添加拖尾效果

為了使得小球運動更加逼真,可以添加拖尾效果。使用clearRect函數清除前一幀動畫時,若用一個半透明的fillRect函數取代之,就可輕松制作長尾效果。

ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);

移動鼠標到canvas內可讓小球動起來!

遺留問題和優化

在實際的生活中,小球碰撞到地面反彈的時候,反彈的高度會越來越低,因為碰撞地面損失了一部分速度。

boundary: function(top, right, bottom, left) {
    // 檢測小球下一幀是否出界,出界則補正
    if (this.y > bottom) {  // 下邊界越界
        this.vy = -this.vy;  // 速度反向
        this.vy = 0.9 * this.vy; // 速度損失
    } else if (this.y < top) {
        this.vy = -this.vy;
    } else if (ball.x > right) {
        this.vx = -this.vx;  // 速度反向
    } else if (ball.x < left) {
        this.vx = -this.vx;
    }
}

上面這種方式會偶爾使得小球無法反彈。

在碰撞地面的時候,小球的反彈之后的速度和位移,准確值需要根據嚴格的勻加速公式以及損失之后的速度來計算。

邊界檢查時上述方法是檢查圓心和邊界的位置,更好的方式是檢查圓周和邊界的距離。

源碼可以以及效果可以參考這兒:本文實例

上述所有方式的源代碼如下:

```html ball animate ```


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM