canvas動畫教程-2 基礎設施

liuyawin000發表於2018-10-07

上一篇介紹了canvas的基本用法和canvas動畫的原理,最後提到為了程式設計方便,我們會封裝一些類。這篇文章對封裝的這些類進行簡單的介紹。

Stage

舞臺。一個專案中只有一個舞臺物件,所有可視物件都被新增進舞臺的children陣列中。舞臺物件擁有一個render屬性,是Renderer類的一個例項,用於對可視物件進行渲染。Stage提供兩個方法,其實現如下:

addChild: function (child) {
    this.children.push(child);
},
render: function () {
    if (this.update) {
        this.update();
    }
    this.renderer.render(this.children);
    requestNextAnimationFrame(this.render.bind(this));
},
複製程式碼

addChild用於將可視物件新增進children中,render方法相當於上一節中提到的animate方法,用於實現動畫迴圈。其中this.update方法為動畫的更新邏輯,每一幀都會執行一次。執行完更新邏輯後重新渲染可視物件,就實現了動畫的效果。

Renderer

用於實現渲染,提供一個render方法:

render: function (displayObjectArr) {
    if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
        return;
    }

    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);

    for (var i = 0, n = displayObjectArr.length; i < n; i++) {
        displayObjectArr[i].draw(this.ctx);
    }
}
複製程式碼

該方法在stage中呼叫,先清除上一幀的內容,然後分別呼叫每一個可視物件的draw方法進行繪製。可視物件的draw方法由可視物件自己實現,這裡只負責呼叫即可。

Loader

用於載入圖片資源。提供三個方法:

add: function(src){
    this._srcArr.push(src);
},
load: function(callback){
    var _this = this;
    var count = 0;
    for (var i = 0; i < this._srcArr.length; i++) {
        var imgObj = this._srcArr[i];
        var src = imgObj.src;
        var img = new Image();
        img.onload = function(){
            count++;
            _this._imgs[imgObj.name] = img;
            if (count === _this._srcArr.length) {
                if (callback) {
                    callback();
                }
            }
        }
        img.onerror = function(){
            count++;
            console.log(src + ' 下載失敗');
        }
        img.src = src;
    }
},
get: function(key){
    return this._imgs[key];
}
複製程式碼

add用於新增圖片物件,該物件包括name和src兩個屬性。load用於實現載入。get方法用於通過圖片的name獲取圖片物件。

Sprite

精靈,是一種可視物件,可以理解為一個整合了動畫的圖形物件,它包含了繪製出這個圖形所需要的所有資訊,包括一個圖片物件,以及它的位置、寬高、角度以及錨點位置等資訊。通過這些資訊,我們就能將精靈物件繪製在canvas畫布上,具體繪製方法如下:

draw: function (ctx) {
    ctx.save();

    ctx.translate(this.x, this.y);
    if (this.rotation) {
        ctx.rotate(this.rotation);
    }
    ctx.translate(-this.pivotX, -this.pivotY);
    ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0,
        this.width || this.image.width, this.height || this.image.height);

    ctx.restore();
}
複製程式碼

通過改變該物件的x、y、width、height、rotation、pivotX和pivotY屬性,就可以改變圖片在當前幀中出現的位置、大小、角度等。

Graphics

是另一種可視物件,用於繪製多邊形。該物件同樣也儲存了繪製一個多邊形所需要的全部資訊,可以用它來繪製線條、矩形、圓等。這個類的實現比較複雜,方法也比較多,不一一介紹,有興趣的可以去看看原始碼。這裡只說明一下它繪製的思想:多邊形物件擁有一個私有陣列屬性_actions,每一個跟繪製邏輯有關的方法其實是往_actions陣列裡新增了一個“動作”,這些動作其實就是繪製的具體步驟。在呼叫draw方法時,會依次執行這些繪製的動作,這樣,多邊形就被繪製在了canvas畫布上。

Polyfill

實現了兩個方法,主要用於相容各個瀏覽器。一個是requestNextAnimationFrame,是requestAnimationFrame的一個polyfill實現,用法與requestAnimationFrame相同,在瀏覽器支援requestAnimationFrame時使用requestAnimationFrame,不支援時則使用setTimeout代替;另一個是函式的bind方法,在瀏覽器支援bind方法時使用原生的bind,不支援時則使用自定義的bind。

例子

OK,寫了這麼多,究竟好不好用?讓我們來試一下:

//定義一個舞臺
var stage = new Stage(document.getElementById('canvas'));

//載入資源
var loader = new Loader();
loader.add({name: 'ball', src: './images/ball.png'});
loader.load(createScene);

//建立場景
function createScene() {
    //建立Sprite的一個例項物件ball
    var ball = new Sprite({
        x: 20,
        y: 20,
        width: 16,
        height: 16,
        pivotX: 32,
        pivotY: 32,
        image: loader.get('ball')
    });
    //將ball加入舞臺
    stage.addChild(ball);
    
    //定義更新邏輯
    stage.update = function () {
        
        ball.x += 2;
        ball.y += 1;

        ball.rotation += 0.1;
        
        if (ball.width < 64) {
            ball.width += 1;
            ball.height += 1;
        }
    }
    
    //開始繪製
    stage.render();
}
}
複製程式碼

不出意外的話,可以看到如下動畫效果:

canvas動畫教程-2 基礎設施

小結

本文對canvas動畫中用到的一些類及其實現進行了介紹,並通過一個例子演示了其用法。由於這個系列文章的主要目的是介紹一些基礎canvas動畫效果的實現原理及方法,而不是實現一個canvas動畫引擎,所以只對這些類進行了簡單的實現,能夠滿足後續演示的需求即可。要深入瞭解比較完備的實現,可以研究一下當前流行的一些框架的原始碼,如阿里的Hilo,以及國外的phaser等。
對於canvas的基礎介紹到這裡就結束了,下一篇文章開始,我們來正式介紹如何實現一些特定的動畫效果。
奉上本系列文章所涉及的原始碼的git地址,喜歡的話麻煩給個star哦!

相關文章列表

相關文章