一次canvas動畫的大筆試
這是一道筆試題,要求是這麼說的 定義了多組座標資料,要實現座標之間的連線動畫(不考慮曲線),以及暫停與播放功能,還有重置,這可太難了呀,還不讓依賴工具,於是我就放棄了。
放棄不可能,還是試一下吧!
技術選型
html css js 用div做.....怎麼做呢...我艹,算了,換canvas吧,還是canvas爽一點。 那麼我特麼為什麼要用canvas呢
- 繪製點到點的動畫,多是利用插值技術,也不排除一些騷操作
- canvas繪製之後不清除是會一直存留的,不必在插值的路徑上去建立畫素
- 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的安全措施,保證安全。
效果也算是完成了,其實還可以有很多拓展的部分,比如曲線連線,回放效果等,不知道你有啥騷操作呢,come on !