最近一直在做canvas動畫效果,發現canvas這個東西做動畫不是不可以。相對於flash,它太底層。如果有給力的編輯器或者給力的框架的話,它就能發揮出更大的威力。
於是決定自己寫一個簡單一點的動畫框架,以便能更方便地構建出一些動畫效果。
我將分幾個章節來講述我這個小動畫框架的實現:
1.通用類的提取:動畫對象與幀對象
2.靈與肉的結合:便於拆卸的運動方程
3.進度條的實現:canvas的圖片預加載
4.demo測試:通過一個demo測試框架
這一節我們先來說說通用類的提取。
其實上一篇文章我已經用到了這種從flash借鑒來的思路:一個動畫對象(類似flash中的元件),一個幀對象(類似flash中的幀)。動畫就是在不斷在當前幀上繪制每個動畫對象來實現的。有了這兩個對象,再加上一些運動方法,我們就可以構建出動畫來。
首先我們先來看看動畫對象Aniele:
/* *Aniele動畫對象 *所有動畫對象的始祖 */ var Aniele=function(){ this.img=new Image(); //定義動畫對象位置 this.loca={ x:300, y:300 } //定義動畫對象的大小(可以實現縮放) this.dw; this.dh; //動畫對象的速度屬性 this.speed={ x:0, y:0 } //設置對象的透明度 this.alpha=1; //設置圖像翻轉,1為不翻轉,-1為翻轉 this.scale={ x:1, y:1 } //定動畫對象的運動方法庫 this.motionFncs=[]; } Aniele.prototype={ //添加運動方法 addMotionFnc:function (name,fnc) { this.motionFncs[name]=fnc; }, //刪除運動方法 deleMotionFnc:function(name){ this.motionFncs[name]=null; }, //遍歷運動方法庫里的所有運動方法 countMotionFncs:function () { for (var i=0; i<this.motionFncs.length; i++) { if(this.motionFncs[i]==null) continue; this.motionFncs[i].call(this); } }, //把自己繪制出來的方法,包括功能:水平翻轉 draw:function(canvas,ctx){ //存儲canvas狀態 ctx.save(); //實現透明度的改變 ctx.globalAlpha=this.alpha; //實現水平豎直翻轉,定義drawImage的兩個位置參數dx,dy var dx=this.loca.x; var dy=this.loca.y; if(this.scale.x!=1||this.scale.y!=1){ if(this.scale.x<0){ console.log(this.img.width) dx=canvas.width-this.loca.x-this.img.width; ctx.translate(canvas.width,1); ctx.scale(this.scale.x,1); } if(this.scale.y<0){ dy=canvas.height-this.loca.y-this.img.height; ctx.translate(1,canvas.height); ctx.scale(1,this.scale.y); } } if(this.dw==null) this.dw=this.img.width; if(this.dh==null) this.dh=this.img.height; //畫出對象 ctx.drawImage(this.img,dx,dy,this.dw,this.dh); //恢復canvas狀態 ctx.restore(); } }
動畫對象的主要屬性:
this.img=new Image();我們引入一張圖片,依附在動畫對象上
this.loca.x等等;圖片的大小位置透明度等等,便於繪圖時調用
this.motionFncs=[];這個比較關鍵,我們給動畫對象定義一個運動方法庫,把動畫對象的運動規則都放在這個運動方法庫中統一管理(每個動畫對象都有自己的運動方法庫)
動畫對象的主要方法:
addMotionFnc: 為動畫對象的運動方法庫中添加一個運動方法
deleMotionFnc:為動畫對象的運動方法庫中刪除一個運動方法
countMotionFncs:為動畫對象遍歷運動方法庫中的所有運動方法
draw:把動畫對象畫在畫布上,這里我們會把畫布作為參數傳到這個方法里面去,便於繪圖
在draw方法里,我封裝了一些對圖像的簡單操作,這些操作在動畫中會經常用到:透明,縮放和翻轉。
有了這個,我們就好似獲得了flash里的一個元件,我們可以通過修改它的屬性來隨意改變它。
那么幀對象呢?
幀對象肩負着渲染的任務,並且管理所有動畫對象:
/* *Render渲染對象 *管理所有動畫對象和渲染 *參數:畫布對象,畫布上下文 */ var Render=function (canvas,ctx) { //引入畫布 this.canvas=canvas; this.ctx=ctx; //創建一個緩沖畫布 this.backBuffer=document.createElement('canvas'); this.backBuffer.width=this.canvas.width; this.backBuffer.height=this.canvas.height; this.backBufferctx=this.backBuffer.getContext('2d'); //所有動畫對象 this.aniEles=[]; } Render.prototype={ //初始化畫布int int:function () { clearInterval(this.sint); this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height); this.backBufferctx.clearRect(0,0,this.backBuffer.width,this.backBuffer.height); }, //設置開始渲染 begin:function () { this.lastFrame=(new Date()).getTime(); this.sint=setInterval((function(progra){ return function(){progra.render();} })(this),SECOND); }, //主渲染方法 render:function () { //在畫布和緩存畫布上清除歷史幀 this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height); this.backBufferctx.clearRect(0,0,this.backBuffer.width,this.backBuffer.height); //保存當前的實時輸出幀率this.ftp this.nowFrame=(new Date()).getTime(); this.ftp=1000/(this.nowFrame-this.lastFrame); this.lastFrame=this.nowFrame; //調用每個動畫對象的運動方法 for (var i=0; i<this.aniEles.length; i++) { if(this.aniEles[i]==null) continue; this.aniEles[i].countMotionFncs(); //把對象繪制到后台緩沖畫布上 this.aniEles[i].draw(this.backBuffer,this.backBufferctx); } //把后台對象繪制到前台 this.ctx.drawImage(this.backBuffer,0,0); }, //增加動畫對象 addAniEle:function (name,aniEle) { this.aniEles[name]=aniEle; }, //刪除動畫對象 deleAniEle:function (name) { this.aniEles[name]=null; } }
幀對象的主要屬性:
this.aniEles=[];用來存儲當前畫布上所有動畫實例的數組
大家用過canvas載入圖片的應該知道,由於圖片的異步載入,動畫過程中圖片會出現閃爍的現象,為了避免這種現象,我采用了雙緩沖。
首先后台創建一個畫布:
this.backBuffer=document.createElement('canvas');
this.backBuffer.width=this.canvas.width;
this.backBuffer.height=this.canvas.height;
this.backBufferctx=this.backBuffer.getContext('2d');
我們所有繪制命令都執行在這個后台畫布上,最后把后台畫布畫在前台畫布上:
this.ctx.drawImage(this.backBuffer,0,0);
這種先把圖繪在后台畫布,再把后台畫布復制到前台的方法就叫做雙緩沖技術。
幀屬性的主要方法:
int:用於初始化畫布
begin:開始動畫渲染的方法
render:主渲染的方法
addAniEle:為當前幀添加動畫對象
deleAniEle:為當前幀刪除動畫
我們利用幀對象的流程是:先為當前幀添加動畫對象,然后讓當前幀開始渲染。