溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何利用緩存來優(yōu)化HTML5 Canvas程序的性能

發(fā)布時間:2021-08-02 16:00:03 來源:億速云 閱讀:97 作者:chen 欄目:web開發(fā)

這篇文章主要介紹“如何利用緩存來優(yōu)化HTML5 Canvas程序的性能”,在日常操作中,相信很多人在如何利用緩存來優(yōu)化HTML5 Canvas程序的性能問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何利用緩存來優(yōu)化HTML5 Canvas程序的性能”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

canvas玩多了后,就會自動的要開始考慮性能問題了。怎么優(yōu)化canvas的動畫呢?

  【使用緩存】

  使用緩存也就是用離屏canvas進行預(yù)渲染了,原理很簡單,就是先繪制到一個離屏canvas中,然后再通過drawImage把離屏canvas畫到主canvas中??赡芸吹竭@很多人就會誤解,這不是寫游戲里面用的很多的雙緩沖機制么?

  其實不然,雙緩沖機制是游戲編程中為了防止畫面閃爍,因此會有一個顯示在用戶面前的畫布以及一個后臺畫布,進行繪制時會先將畫面內(nèi)容繪制到后臺畫布中,再將后臺畫布里的數(shù)據(jù)繪制到前臺畫布中。這就是雙緩沖,但是canvas中是沒有雙緩沖的,因為現(xiàn)代瀏覽器基本上都是內(nèi)置了雙緩沖機制。所以,使用離屏canvas并不是雙緩沖,而是把離屏canvas當成一個緩存區(qū)。把需要重復(fù)繪制的畫面數(shù)據(jù)進行緩存起來,減少調(diào)用canvas的API的消耗。

  眾所周知,調(diào)用canvas的API很消耗性能,所以,當我們要繪制一些重復(fù)的畫面數(shù)據(jù)時,妥善利用離屏canvas對性能方面有很大的提升,可以看下下面的DEMO

  1 、 沒使用緩存   

  2、  使用了緩存但是沒有設(shè)置離屏canvas的寬高  

      3 、 使用了緩存但是沒有設(shè)置離屏canvas的寬高  

  4 、 使用了緩存且設(shè)置了離屏canvas的寬高

  可以看到上面的DEMO的性能不一樣,下面分析一下原因:為了實現(xiàn)每個圈的樣式,所以繪制圈圈時我用了循環(huán)繪制,如果沒用啟用緩存,當頁面的圈圈數(shù)量達到一定時,動畫每一幀就要大量調(diào)用canvas的API,要進行大量的計算,這樣再好的瀏覽器也會被拖垮啦。
XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. ctx.save();   

  2.                         var j=0;   

  3.                         ctx.lineWidth = borderWidth;   

  4.                         for(var i=1;i<this.r;i+=borderWidth){   

  5.                             ctx.beginPath();   

  6.                             ctx.strokeStyle = this.color[j];   

  7.                             ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);   

  8.                             ctx.stroke();   

  9.                             j++;   

  10.                         }   

  11.                         ctx.restore();  

  所以,我的方法很簡單,每個圈圈對象里面給他一個離屏canvas作緩存區(qū)。

  除了創(chuàng)建離屏canvas作為緩存之外,下面的代碼中有一點很關(guān)鍵,就是要設(shè)置離屏canvas的寬度和高度,canvas生成后的默認大小是300X150;對于我的代碼中每個緩存起來圈圈對象半徑最大也就不超過80,所以300X150的大小明顯會造成很多空白區(qū)域,會造成資源浪費,所以就要設(shè)置一下離屏canvas的寬度和高度,讓它跟緩存起來的元素大小一致,這樣也有利于提高動畫性能。上面的四個demo很明顯的顯示出了性能差距,如果沒有設(shè)置寬高,當頁面超過400個圈圈對象時就會卡的不行了,而設(shè)置了寬高1000個圈圈對象也不覺得卡。

XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. var ball = function(x , y , vx , vy , useCache){   

  2.                 this.x = x;   

  3.                 this.y = y;   

  4.                 this.vx = vx;   

  5.                 this.vy = vy;   

  6.                 this.r = getZ(getRandom(20,40));   

  7.                 this.color = [];   

  8.                 this.cacheCanvas = document.createElement("canvas");   

  9.                 thisthis.cacheCtx = this.cacheCanvas.getContext("2d");   

  10.                 this.cacheCanvas.width = 2*this.r;   

  11.                 this.cacheCanvas.height = 2*this.r;   

  12.                 var num = getZ(this.r/borderWidth);   

  13.                 for(var j=0;j<num;j++){   

  14.                     this.color.push("rgba("+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+",1)");   

  15.                 }   

  16.                 this.useCache = useCache;   

  17.                 if(useCache){   

  18.                     this.cache();   

  19.                 }   

  20.             }  

當我實例化圈圈對象時,直接調(diào)用緩存方法,把復(fù)雜的圈圈直接畫到圈圈對象的離屏canvas中保存起來。

XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. cache:function(){   

  2.                     this.cacheCtx.save();   

  3.                     var j=0;   

  4.                     this.cacheCtx.lineWidth = borderWidth;   

  5.                     for(var i=1;i<this.r;i+=borderWidth){   

  6.                         this.cacheCtx.beginPath();   

  7.                         thisthis.cacheCtx.strokeStyle = this.color[j];   

  8.                         this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);   

  9.                         this.cacheCtx.stroke();   

  10.                         j++;   

  11.                     }   

  12.                     this.cacheCtx.restore();   

  13.                 }  

然后在接下來的動畫中,我只需要把圈圈對象的離屏canvas畫到主canvas中,這樣,每一幀調(diào)用的canvasAPI就只有這么一句話:

XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);  

跟之前的for循環(huán)繪制比起來,實在是快太多了。所以當需要重復(fù)繪制矢量圖的時候或者繪制多個圖片的時候,我們都可以合理利用離屏canvas來預(yù)先把畫面數(shù)據(jù)緩存起來,在接下來的每一幀中就能減少很多沒必要的消耗性能的操作。

下面貼出1000個圈圈對象流暢版代碼:
  

XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. <!doctype html>  

  2. <html lang="en">  

  3. <head>  

  4.     <meta charset="UTF-8">  

  5.     <style>  

  6.         body{   

  7.             padding:0;   

  8.             margin:0;   

  9.             overflow: hidden;   

  10.         }   

  11.         #cas{   

  12.             display: block;   

  13.             background-color:rgba(0,0,0,0);   

  14.             margin:auto;   

  15.             border:1px solid;   

  16.         }   

  17.     </style>  

  18.     <title>測試</title>  

  19. </head>  

  20. <body>  

  21.     <div >  

  22.         <canvas id='cas' width="800" height="600">瀏覽器不支持canvas</canvas>  

  23.         <div style="text-align:center">1000個圈圈對象也不卡</div>  

  24.     </div>  

  25.   

  26.     <script>  

  27.         var testBox = function(){   

  28.             var canvas = document.getElementById("cas"),   

  29.                 ctx = canvas.getContext('2d'),   

  30.                 borderWidth = 2,   

  31.                 Balls = [];   

  32.             var ball = function(x , y , vx , vy , useCache){   

  33.                 this.x = x;   

  34.                 this.y = y;   

  35.                 this.vx = vx;   

  36.                 this.vy = vy;   

  37.                 this.r = getZ(getRandom(20,40));   

  38.                 this.color = [];   

  39.                 this.cacheCanvas = document.createElement("canvas");   

  40.                 thisthis.cacheCtx = this.cacheCanvas.getContext("2d");   

  41.                 this.cacheCanvas.width = 2*this.r;   

  42.                 this.cacheCanvas.height = 2*this.r;   

  43.                 var num = getZ(this.r/borderWidth);   

  44.                 for(var j=0;j<num;j++){   

  45.                     this.color.push("rgba("+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+",1)");   

  46.                 }   

  47.                 this.useCache = useCache;   

  48.                 if(useCache){   

  49.                     this.cache();   

  50.                 }   

  51.             }   

  52.   

  53.             function getZ(num){   

  54.                 var rounded;   

  55.                 rounded = (0.5 + num) | 0;   

  56.                 // A double bitwise not.   

  57.                 rounded = ~~ (0.5 + num);   

  58.                 // Finally, a left bitwise shift.   

  59.                 rounded = (0.5 + num) << 0;   

  60.   

  61.                 return rounded;   

  62.             }   

  63.   

  64.             ball.prototype = {   

  65.                 paint:function(ctx){   

  66.                     if(!this.useCache){   

  67.                         ctx.save();   

  68.                         var j=0;   

  69.                         ctx.lineWidth = borderWidth;   

  70.                         for(var i=1;i<this.r;i+=borderWidth){   

  71.                             ctx.beginPath();   

  72.                             ctx.strokeStyle = this.color[j];   

  73.                             ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);   

  74.                             ctx.stroke();   

  75.                             j++;   

  76.                         }   

  77.                         ctx.restore();   

  78.                     } else{   

  79.                         ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);   

  80.                     }   

  81.                 },   

  82.   

  83.                 cache:function(){   

  84.                     this.cacheCtx.save();   

  85.                     var j=0;   

  86.                     this.cacheCtx.lineWidth = borderWidth;   

  87.                     for(var i=1;i<this.r;i+=borderWidth){   

  88.                         this.cacheCtx.beginPath();   

  89.                         thisthis.cacheCtx.strokeStyle = this.color[j];   

  90.                         this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);   

  91.                         this.cacheCtx.stroke();   

  92.                         j++;   

  93.                     }   

  94.                     this.cacheCtx.restore();   

  95.                 },   

  96.   

  97.                 move:function(){   

  98.                     this.x += this.vx;   

  99.                     this.y += this.vy;   

  100.                     if(this.x>(canvas.width-this.r)||this.x<this.r){   

  101.                         thisthis.x=this.x<this.r?this.r:(canvas.width-this.r);   

  102.                         this.vx = -this.vx;   

  103.                     }   

  104.                     if(this.y>(canvas.height-this.r)||this.y<this.r){   

  105.                         thisthis.y=this.y<this.r?this.r:(canvas.height-this.r);   

  106.                         this.vy = -this.vy;   

  107.                     }   

  108.   

  109.                     this.paint(ctx);   

  110.                 }   

  111.             }   

  112.   

  113.             var Game = {   

  114.                 init:function(){   

  115.                     for(var i=0;i<1000;i++){   

  116.                         var b = new ball(getRandom(0,canvas.width) , getRandom(0,canvas.height) , getRandom(-10 , 10) ,  getRandom(-10 , 10) , true)   

  117.                         Balls.push(b);   

  118.                     }   

  119.                 },   

  120.   

  121.                 update:function(){   

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

  123.                     for(var i=0;i<Balls.length;i++){   

  124.                         Balls[i].move();   

  125.                     }   

  126.                 },   

  127.   

  128.                 loop:function(){   

  129.                     var _this = this;   

  130.                     this.update();   

  131.                     RAF(function(){   

  132.                         _this.loop();   

  133.                     })   

  134.                 },   

  135.   

  136.                 start:function(){   

  137.                     this.init();   

  138.                     this.loop();   

  139.                 }   

  140.             }   

  141.   

  142.             window.RAF = (function(){   

  143.                 return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };   

  144.             })();   

  145.   

  146.             return Game;   

  147.         }();   

  148.   

  149.         function getRandom(a , b){   

  150.             return Math.random()*(b-a)+a;   

  151.         }   

  152.   

  153.         window.onload = function(){   

  154.             testBox.start();   

  155.         }   

  156.     </script>  

  157. </body>  

  158. </html>  

  離屏canvas還有一個注意事項,如果你做的效果是會將對象不停地創(chuàng)建和銷毀,請慎重使用離屏canvas,至少不要像我上面寫的那樣給每個對象的屬性綁定離屏canvas。

  因為如果這樣綁定,當對象被銷毀時,離屏canvas也會被銷毀,而大量的離屏canvas不停地被創(chuàng)建和銷毀,會導(dǎo)致canvas buffer耗費大量GPU資源,容易造成瀏覽器崩潰或者嚴重卡幀現(xiàn)象。解決辦法就是弄一個離屏canvas數(shù)組,預(yù)先裝進足夠數(shù)量的離屏canvas,僅將仍然存活的對象緩存起來,當對象被銷毀時,再解除緩存。這樣就不會導(dǎo)致離屏canvas被銷毀了。

 【使用requestAnimationFrame】

  這個就不具體解釋了,估計很多人都知道,這個才是做動畫的最佳循環(huán),而不是setTimeout或者setInterval。直接貼出兼容性寫法:

XML/HTML Code復(fù)制內(nèi)容到剪貼板

  1. window.RAF = (function(){   

  2.        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };   

  3.             })();   

  【避免浮點運算】

  雖然javascript提供了很方便的一些取整方法,像Math.floor,Math.ceil,parseInt,但是,國外友人做過測試,parseInt這個方法做了一些額外的工作(比如檢測數(shù)據(jù)是不是有效的數(shù)值,parseInt 甚至先將參數(shù)轉(zhuǎn)換成了字符串!),所以,直接用parseInt的話相對來說比較消耗性能,那怎樣取整呢,可以直接用老外寫的很巧妙的方法了:

    JavaScript Code復(fù)制內(nèi)容到剪貼板

    1.rounded = (0.5 + somenum) | 0;      

    2.rounded = ~~ (0.5 + somenum);   3.rounded = (0.5 + somenum) << 0;      

  1. 運算符不懂的可以直接戳:http://www.w3school.com.cn/js/pro_js_operators_bitwise.asp  里面有詳細解釋

  【盡量減少canvasAPI的調(diào)用】

  作粒子效果時,盡量少使用圓,最好使用方形,因為粒子太小,所以方形看上去也跟圓差不多。至于原因,很容易理解,我們畫一個圓需要三個步驟:先beginPath,然后用arc畫弧,再用fill進行填充才能產(chǎn)生一個圓。但是畫方形,只需要一個fillRect就可以了。雖然只是差了兩個調(diào)用,當粒子對象數(shù)量達到一定時,這性能差距就會顯示出來了。

  還有一些其他注意事項,我就不一一列舉了,因為谷歌上一搜也挺多的。我這也算是一個給自己做下記錄,主要是記錄緩存的用法。想要提升canvas的性能最主要的還是得注意代碼的結(jié)構(gòu),減少不必要的API調(diào)用,在每一幀中減少復(fù)雜的運算或者把復(fù)雜運算由每一幀算一次改成數(shù)幀算一次。同時,上面所述的緩存用法,我因為貪圖方便,所以是每個對象一個離屏canvas,其實離屏canvas也不能用的太泛濫,如果用太多離屏canvas也會有性能問題,請盡量合理利用離屏canvas。

   源碼地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Other-demo/cache

到此,關(guān)于“如何利用緩存來優(yōu)化HTML5 Canvas程序的性能”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI