一次canvas動畫的大筆試

山鬼發表於2019-03-08

這是一道筆試題,要求是這麼說的 定義了多組座標資料,要實現座標之間的連線動畫(不考慮曲線),以及暫停與播放功能,還有重置,這可太難了呀,還不讓依賴工具,於是我就放棄了。


放棄不可能,還是試一下吧!

技術選型

html css js 用div做.....怎麼做呢...我艹,算了,換canvas吧,還是canvas爽一點。 那麼我特麼為什麼要用canvas呢

  1. 繪製點到點的動畫,多是利用插值技術,也不排除一些騷操作
  2. canvas繪製之後不清除是會一直存留的,不必在插值的路徑上去建立畫素
  3. canvas對互動動畫有著一定的天然優勢,如果人家非要讓我用div或者svg,那我也只要自閉了

建立依賴

依賴總是我最喜歡寫的東西,2D總是離不開Vector2,開始構建我們可愛的VV

var Vector = function(x = 0,y = 0) {
    this.x = x;
    this.y = y;
}
複製程式碼

然後稍微加工一下

Vector.prototype={
    add:function(v){
        return new Vector(this.x+v.x,this.y+v.y);
    },
    subtract:function(v){
        return new Vector(v.x-this.x,v.y-this.y);
    },
    length:function(){
        return Math.sqrt(this.x*this.x+this.y*this.y);
    },
    divide:function(n){
        return new Vector(this.x/n,this.y/n);
    },
    unit:function(){
        return this.divide(this.length());
    },
    lerp:function(v){
        let dirV = this.subtract(v);
        let unit = dirV.unit();
        return this.add(unit);

    }
}
複製程式碼

如此一來,基本的核心功能就出來了

BALL的構建

至於為什麼叫BALL,也許是因為熱衷於球吧。

var ball = function(x = 0,y = 0 ){
    this.x = x;
    this.y = y;
    this.status = MOVEING;

}
ball.prototype.render = function(context){
    let self = this;
    context.save();
    context.fillStyle = fillStyle;
    context.rect(self.x, self.y,5,5);
    context.fill();
    context.restore();
}
ball.prototype.lerp = function(site){
    //插值計算,不斷更新座標
    let self = this;
    let dirV = new Vector(...site);
    self.site = new Vector(this.x,this.y);
    let n_site = self.site.lerp(dirV);
    self.x = n_site.x;
    self.y = n_site.y;
}
複製程式碼

正如你所見,利用了向量的插值計算來不斷更新當前座標來實現超速移動。 使用status來處理不同狀態的BALL,善於使用狀態量會使程式碼邏輯更加舒適,這裡定義了所需要的狀態

const PEDDING = 'PEDDING';
const MOVEING = 'MOVING';
const RESETING ='RESETING';
複製程式碼

主邏輯

整體的主要邏輯就是不斷對BALL進行插值更新,同時要處理小球的狀態

function move(pos){
    context.clearRect(0, 0, can.width, can.height);
    a.render(context);
    switch (a.status) {
        case 'PEDDING':
            
            break;
        case 'MOVING':
            a.lerp(pos);
            break;
        // case 'RESETING':
        //     //小球RESETING狀態,有BUG,可有可無
        //     a = null;
        //     context.clearRect(0, 0, can.width, can.height);
        //     a = new ball(...site[0]);
        //     index=0;
        //     //a.status = MOVEING;          
        //     break;
        default:
            break;
    }
}
複製程式碼

不要太在意那個BUG,程式設計師的BUG能叫BUG嗎?

渲染部分

function run(){
    let x = site[index+1][0];
    let y = site[index+1][1];
    console.log(index)
    if(a.x>=x-1&&a.y>=y-1){
        index=index+1;
    }
    if(site[index+1]){
        move(site[index+1]);
        requestAnimationFrame(run)
    }
    if(a.status==RESETING){
        context.clearRect(0, 0,2000,1000);
        //這裡棄用
    }
}
(function(){
    site.length>=2?requestAnimationFrame(run):' ';
})()
複製程式碼

這裡只需要注意座標序列的長度,避免只有一個座標,還有就是當前索引歸屬於下一個索引的存在與否。

這裡是個小TIP,不要再動畫的迴圈中寫過多的邏輯,能封裝出去的邏輯儘量封裝出去,不然會顯得程式碼異常臃腫。

互動部分

    <button onclick="pause()">暫停</button>
    <button onclick="play()">播放</button>  
    <button onclick="reset()">重置</button>   
複製程式碼
//重置 暫停 執行
function reset(){  
    location.reload();
    //a.status = RESETING;
}
function pause(){
    a.status = PEDDING;
}
function play(){
    a.status = MOVEING;
}
複製程式碼

由於canvas的clearRect有點問題,效果一直出不來,這裡直接採用強行F5的安全措施,保證安全。

測試

一次canvas動畫的大筆試
效果也算是完成了,其實還可以有很多擴充的部分,比如曲線連線,回放效果等,不知道你有啥騷操作呢,come on ! 原始碼下載:

相關文章