Home  >  Article  >  Web Front-end  >  A brief discussion on using cache to optimize the performance of HTML5 Canvas program_html5 tutorial skills

A brief discussion on using cache to optimize the performance of HTML5 Canvas program_html5 tutorial skills

WBOY
WBOYOriginal
2016-05-16 15:46:482564browse

After you play with canvas too much, you will automatically start to consider performance issues. How to optimize canvas animation?

【Use cache】

Using cache means using off-screen canvas for pre-rendering. The principle is very simple, that is, first draw into an off-screen canvas, and then draw the off-screen canvas into the main canvas through drawImage. Many people may misunderstand this. Isn't this a double-buffering mechanism used a lot in games?

In fact, the double buffering mechanism is used in game programming to prevent screen flickering. Therefore, there will be a canvas displayed in front of the user and a background canvas. When drawing, the screen content will first be drawn to the background canvas, and then the background canvas will be drawn. The data in the canvas is drawn to the front canvas. This is double buffering, but there is no double buffering in canvas, because modern browsers basically have a built-in double buffering mechanism. Therefore, using off-screen canvas is not double buffering, but treating off-screen canvas as a cache area. Cache screen data that needs to be drawn repeatedly to reduce the consumption of calling the canvas API.

As we all know, calling the canvas API consumes performance. Therefore, when we want to draw some repeated screen data, proper use of off-screen canvas can greatly improve performance. You can take a look at the following DEMO

1. No cache is used

 2. Cache is used but the width and height of the off-screen canvas are not set

3. Cache is used but the width and height of the off-screen canvas are not set

 4. Use caching and set the width and height of the off-screen canvas

You can see that the performance of the above DEMO is different. Let’s analyze the reasons below: In order to achieve the style of each circle, I used loop drawing when drawing circles. If caching is not enabled, when the number of circles on the page When it reaches a certain point, a large number of canvas API calls are required for each frame of the animation, and a large amount of calculations are required, so that no matter how good the browser is, it will be brought down.
XML/HTML CodeCopy content to clipboard

  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作缓存区。

  除了创建离屏canvas作为缓存之外,下面的代码中有一点很关键,就是要设置离屏canvas的宽度和高度,canvas生成后的默认大小是300X150;对于我的代码中每个缓存起来圈圈对象半径最大也就不超过80,所以300X150的大小明显会造成很多空白区域,会造成资源浪费,所以就要设置一下离屏canvas的宽度和高度,让它跟缓存起来的元素大小一致,这样也有利于提高动画性能。上面的四个demo很明显的显示出了性能差距,如果没有设置宽高,当页面超过400个圈圈对象时就会卡的不行了,而设置了宽高1000个圈圈对象也不觉得卡。

XML/HTML Code复制内容到剪贴板
  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.             }  

When I instantiate the circle object, I directly call the cache method and draw the complex circle directly into the off-screen canvas of the circle object and save it.

XML/HTML CodeCopy content to clipboard
  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.                                                                                     
  14. Then in the next animation, I only need to draw the off-screen canvas of the circle object into the main canvas. In this way, the canvasAPI called in each frame only has this sentence:

XML/HTML Code

Copy content to clipboard
ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);
  1. Compared with the previous for loop drawing, it is really much faster. So when we need to repeatedly draw vector graphics or draw multiple pictures, we can reasonably use the off-screen canvas to cache the picture data in advance, which can reduce a lot of unnecessary performance consumption in each subsequent frame. operation.
The smooth version code for 1000 circle objects is posted below:
 


XML/HTML Code

Copy content to clipboard
  1. >  
  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>Testtitle>
  19. head>
  20. <body>
  21. <div >
  22.  <canvas id='cas' width="800" height="600">The browser does not support canvascanvas> ;
  23.  <div style="text- align:center">1000 circle objects are not stuckdiv>
  24.  div> 
  25.  <script> 
  26. var testBox = function(){
  27. var canvas = document.getElementById("cas"),
  28. ctx = canvas.getContext('2d'),
  29.  borderWidth = 2,
  30. Balls = [];
  31. var ball = function(x, y, vx, vy, useCache){
  32.  this.x = x;
  33.  this.y = y;
  34.  this.vx = vx;
  35.                 this.vy = vy;   
  36.                 this.r = getZ(getRandom(20,40));   
  37.                 this.color = [];   
  38.                 this.cacheCanvas = document.createElement("canvas");   
  39.                 thisthis.cacheCtx = this.cacheCanvas.getContext("2d");   
  40.                 this.cacheCanvas.width = 2*this.r;   
  41.                 this.cacheCanvas.height = 2*this.r;   
  42.                 var num = getZ(this.r/borderWidth);   
  43.                 for(var j=0;j<num;j ){   
  44.                     this.color.push("rgba(" getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) ",1)");   
  45.                 }   
  46.                 this.useCache = useCache;   
  47.                 if(useCache){   
  48.                     this.cache();   
  49.                 }   
  50.             }  
  51.   
  52.             function getZ(num){   
  53.                 var rounded;   
  54.                 rounded = (0.5   num) | 0;   
  55.                 // A double bitwise not.   
  56.                 rounded = ~~ (0.5   num);   
  57.                 // Finally, a left bitwise shift.   
  58.                 rounded = (0.5   num) << 0;   
  59.   
  60.                 return rounded;   
  61.             }  
  62.   
  63.             ball.prototype = {   
  64.                 paint:function(ctx){   
  65.                     if(!this.useCache){   
  66.                         ctx.save();   
  67.                         var j=0;   
  68.                         ctx.lineWidth = borderWidth;   
  69.                         for(var i=1;i<this.r;i =borderWidth){   
  70.                             ctx.beginPath();   
  71.                             ctx.strokeStyle = this.color[j];   
  72.                             ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);   
  73.                             ctx.stroke();   
  74.                             j ;   
  75.                         }   
  76.                         ctx.restore();   
  77.                     } else{   
  78.                         ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);   
  79.                     }   
  80.                 },   
  81.   
  82.                 cache:function(){   
  83.                     this.cacheCtx.save();   
  84.                     var j=0;   
  85.                     this.cacheCtx.lineWidth = borderWidth;   
  86.                     for(var i=1;i<this.r;i =borderWidth){   
  87.                         this.cacheCtx.beginPath();   
  88.                         thisthis.cacheCtx.strokeStyle = this.color[j];   
  89.                         this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);   
  90.                         this.cacheCtx.stroke();   
  91.                         j ;   
  92.                     }   
  93.                     this.cacheCtx.restore();   
  94.                 },   
  95.   
  96.                 move:function(){   
  97.                     this.x  = this.vx;   
  98.                     this.y  = this.vy;   
  99.                     if(this.x>(canvas.width-this.r)||this.x<this.r){   
  100.                         thisthis.x=this.x<this.r?this.r:(canvas.width-this.r);   
  101.                         this.vx = -this.vx;   
  102.                     }  
  103.                     if(this.y>(canvas.height-this.r)||this.y<this.r){   
  104.                         thisthis.y=this.y<this.r?this.r:(canvas.height-this.r);   
  105.                         this.vy = -this.vy;   
  106.                     }   
  107.   
  108.                     this.paint(ctx);   
  109.                 }   
  110.             }   
  111.   
  112.             var Game = {   
  113.                 init:function(){   
  114.                     for(var i=0;i<1000;i ){   
  115.                         var b = new ball(getRandom(0,canvas.width) , getRandom(0,canvas.height) , getRandom(-10 , 10) ,  getRandom(-10 , 10) , true)   
  116.                         Balls.push(b);   
  117.                     }   
  118.                 },   
  119.   
  120.                 update:function(){   
  121.                     ctx.clearRect(0,0,canvas.width,canvas.height);   
  122.                     for(var i=0;i<Balls.length;i ){   
  123.                         Balls[i].move();   
  124.                     }   
  125.                 },   
  126.   
  127.                 loop:function(){   
  128.                     var _this = this;   
  129.                     this.update();   
  130.                     RAF(function(){   
  131.                         _this.loop();   
  132.                     })   
  133.                 },   
  134.   
  135.                 start:function(){   
  136.                     this.init();   
  137.                     this.loop();   
  138.                 }   
  139.             }   
  140.   
  141.             window.RAF = (function(){   
  142.                 return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };   
  143.             })();   
  144.   
  145.             return Game;   
  146.         }();   
  147.   
  148.         function getRandom(a , b){   
  149.             return Math.random()*(b-a) a;   
  150.         }   
  151.   
  152.         window.onload = function(){   
  153.             testBox.start();   
  154.         }   
  155.     script>  
  156. body>  
  157. html>  

There is another note about off-screen canvas. If the effect you do is to continuously create and destroy objects, please use off-screen canvas with caution. At least do not bind the attributes of each object as I wrote above. Set off-screen canvas.

Because if bound like this, when the object is destroyed, the off-screen canvas will also be destroyed, and a large number of off-screen canvases are constantly being created and destroyed, which will cause the canvas buffer to consume a lot of GPU resources and easily cause the browser to Crash or serious frame freeze. The solution is to create an off-screen canvas array, preload a sufficient number of off-screen canvases, cache only the objects that are still alive, and uncache them when the objects are destroyed. This will not cause the off-screen canvas to be destroyed.

【Use requestAnimationFrame】

I won’t explain this in detail. I guess many people know that this is the best loop for animation, not setTimeout or setInterval. Directly post the compatibility writing method:

XML/HTML CodeCopy content to clipboard
  1. window.RAF = (function(){
  2. Return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60 ); };
  3. })();

 

【Avoid floating point operations】

Although JavaScript provides some very convenient rounding methods, such as Math.floor, Math.ceil, and parseInt, foreign friends have done tests and the parseInt method does some extra work (such as detecting whether the data is valid value, parseInt even converts the parameter into a string first!), so using parseInt directly is relatively more performance-intensive. So how to round up, you can directly use a very clever method written by foreigners:

    JavaScript CodeCopy content to clipboard

    1.rounded = (0.5 somenum) | 0;

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

    If you don’t understand the operators, you can just click here: http://www.w3school.com.cn/js/pro_js_operators_bitwise.asp There are detailed explanations inside

 

【Reduce canvasAPI calls as much as possible】

When making particle effects, try to use circles as little as possible and preferably use squares. Because the particles are too small, squares look similar to circles. As for the reason, it is easy to understand. We need three steps to draw a circle: first beginPath, then use arc to draw an arc, and then use fill to fill it to produce a circle. But to draw a square, you only need one fillRect. Although there is only a difference of two calls, when the number of particle objects reaches a certain level, the performance gap will show up.

There are some other things to note, I won’t list them all because there are quite a few on Google. This can be regarded as a record for myself, mainly to record the usage of cache. If you want to improve the performance of canvas, the most important thing is to pay attention to the structure of the code, reduce unnecessary API calls, reduce complex operations in each frame, or change complex operations from once for each frame to once for several frames. At the same time, for the cache usage mentioned above, for convenience, I used an off-screen canvas for each object. In fact, off-screen canvas cannot be used too extensively. If you use too many off-screen canvases, there will be performance problems. Please try your best. Make reasonable use of off-screen canvas.

Source code address: https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Other-demo/cache

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn