一、問題描述
在畫canvas時,遇到屏幕瞬間空白的情況(大約1~2幀),造成用戶體驗不好。
二、原因
canvas的繪圖過程是:先擦出整個畫布;然后瀏覽器到達重繪時間點后,在空白的canvas上作畫;xx毫秒后,這一幀動畫上的所有元件完成繪畫。
那么,當采用setTimeout或setInterval等與瀏覽器重繪頻率不同步的計時器對畫布進行繪圖時,很可能上一幀的元件內容還沒被完全畫出來時,不精准的計時器已經驅動着擦除畫布開始下一幀繪畫;以及這一幀繪畫已經結束,但不精准的計時器還沒到指定刷新時間,所以此時會出現明顯空白,也即“閃屏”。
三、解決
雙緩沖理論:閃爍是圖形編程的一個常見問題。需要多重復雜繪制操作的圖形操作會導致呈現的圖像閃爍或具有其他不可接受的外觀。雙緩沖的使用解決這些問題。雙緩沖使用內存緩沖區來解決由多重繪制操作造成的閃爍問題。當啟用雙緩沖時,所有繪制操作首先呈現到內存緩沖區,而不是屏幕上的繪圖圖面。所有繪制操作完成后,內存緩沖區直接復制到與其關聯的繪圖圖面。因為在屏幕上只執行一個圖形操作,所以消除了由復雜繪制操作造成的圖像閃爍。
即,先創建一個cacheCanvas執行計算繪制的操作,當下一幀刷新時,直接將cacheCanvas的內容drawImage到真正的canvas上,這么做既能有效規避閃屏,又能減緩卡頓情況。
示例代碼:
var testBox = function(){ var canvas = document.getElementById("cas"), ctx = canvas.getContext('2d'), borderWidth = 2, Balls = []; var ball = function(x , y , vx , vy , useCache){ this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.r = getZ(getRandom(20,40)); this.color = []; this.cacheCanvas = document.createElement("canvas"); this.cacheCtx = this.cacheCanvas.getContext("2d"); this.cacheCanvas.width = 2*this.r; this.cacheCanvas.height = 2*this.r; var num = getZ(this.r/borderWidth); for(var j=0;j<num;j++){ this.color.push("rgba("+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+",1)"); } this.useCache = useCache; if(useCache){ this.cache(); } } function getZ(num){ var rounded; rounded = (0.5 + num) | 0; // A double bitwise not. rounded = ~~ (0.5 + num); // Finally, a left bitwise shift. rounded = (0.5 + num) << 0; return rounded; } ball.prototype = { paint:function(ctx){ if(!this.useCache){ ctx.save(); var j=0; ctx.lineWidth = borderWidth; for(var i=1;i<this.r;i+=borderWidth){ ctx.beginPath(); ctx.strokeStyle = this.color[j]; ctx.arc(this.x , this.y , i , 0 , 2*Math.PI); ctx.stroke(); j++; } ctx.restore(); } else{ ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r); } }, cache:function(){ this.cacheCtx.save(); var j=0; this.cacheCtx.lineWidth = borderWidth; for(var i=1;i<this.r;i+=borderWidth){ this.cacheCtx.beginPath(); this.cacheCtx.strokeStyle = this.color[j]; this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI); this.cacheCtx.stroke(); j++; } this.cacheCtx.restore(); }, move:function(){ this.x += this.vx; this.y += this.vy; if(this.x>(canvas.width-this.r)||this.x<this.r){ this.x=this.x<this.r?this.r:(canvas.width-this.r); this.vx = -this.vx; } if(this.y>(canvas.height-this.r)||this.y<this.r){ this.y=this.y<this.r?this.r:(canvas.height-this.r); this.vy = -this.vy; } this.paint(ctx); } } var Game = { init:function(){ for(var i=0;i<1000;i++){ var b = new ball(getRandom(0,canvas.width) , getRandom(0,canvas.height) , getRandom(-10 , 10) , getRandom(-10 , 10) , true) Balls.push(b); } }, update:function(){ ctx.clearRect(0,0,canvas.width,canvas.height); for(var i=0;i<Balls.length;i++){ Balls[i].move(); } }, loop:function(){ var _this = this; this.update(); RAF(function(){ _this.loop(); }) }, start:function(){ this.init(); this.loop(); } } window.RAF = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); }; })(); return Game; }(); function getRandom(a , b){ return Math.random()*(b-a)+a; } window.onload = function(){ testBox.start(); }