I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches.
The first fiddle: http://jsfiddle.net/6eypdhjp/
Uses fillRect
with a 4 by 4 white square, providing roughly 60 frames per second @ 10,000 snow flakes.
So I wondered if I could improve this and found a bit of information on HTML5Rocks’ website regarding canvas performance. One such suggestion was to pre-render the snow flakes to canvases and then draw the canvases using drawImage
.
The suggestion is here http://www.html5rocks.com/en/tutorials/canvas/performance/, namely under the title Pre-render to an off-screen canvas. Use Ctrl + f to find that section.
So I tried their suggestion with this fiddle: http://jsfiddle.net/r973sr7c/
How ever, I get about 3 frames per second @ 10,000 snow flakes. Which is very odd given jsPerf even shows a performance boost here using the same method http://jsperf.com/render-vs-prerender
The code I used for pre-rendering is here:
//snowflake particles var mp = 10000; //max particles var particles = []; for(var i = 0; i < mp; i++) { var m_canvas = document.createElement('canvas'); m_canvas.width = 4; m_canvas.height = 4; var tmp = m_canvas.getContext("2d"); tmp.fillStyle = "rgba(255,255,255,0.8)"; tmp.fillRect(0,0,4,4); particles.push({ x : Math.random()*canvas.width, //x-coordinate y : Math.random()*canvas.height, //y-coordinate r : Math.random()*4+1, //radius d : Math.random()*mp, //density img: m_canvas //tiny canvas }) } //Lets draw the flakes function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for(var i = 0; i < particles.length; i++) { var flake = particles[i]; ctx.drawImage(flake.img, flake.x,flake.y); } }
So I wondered why am I getting such horrendous frame rate? And is there any better way to get higher particle counts moving on screen whilst maintaining 60 frames per second?
Advertisement
Answer
Best frame rates are achieved by drawing pre-rendered images (or pre-rendered canvases).
You could refactor your code to:
- Create about 2-3 offscreen (in-memory) canvases each with 1/3 of your particles drawn on them
- Assign each canvas a fallrate and a driftrate.
- In each animation frame, draw each offscreen canvas (with an offset according to its own fallrate & driftrate) onto the on-screen canvas.
The result should be about 60 frames-per-second.
This technique trades increased memory usage to achieve maximum frame rates.
Here’s example code and a Demo:
var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); var cw=canvas.width; var ch=canvas.height; var mp=10000; var particles=[]; var panels=[]; var panelCount=2; var pp=panelCount-.01; var maxFallrate=2; var minOffsetX=-parseInt(cw*.25); var maxOffsetX=0; // create all particles for(var i=0;i<mp;i++){ particles.push({ x: Math.random()*cw*1.5, //x-coordinate y: Math.random()*ch, //y-coordinate r: 1, //radius panel: parseInt(Math.random()*pp) // panel==0 thru panelCount }) } // create a canvas for each panel var drift=.25; for(var p=0;p<panelCount;p++){ var c=document.createElement('canvas'); c.width=cw*1.5; c.height=ch*2; var offX=(drift<0)?minOffsetX:maxOffsetX; panels.push({ canvas:c, ctx:c.getContext('2d'), offsetX:offX, offsetY:-ch, fallrate:2+Math.random()*(maxFallrate-1), driftrate:drift }); // change to opposite drift direction for next panel drift=-drift; } // pre-render all particles // on the specified panel canvases for(var i=0;i<particles.length;i++){ var p=particles[i]; var cctx=panels[p.panel].ctx; cctx.fillStyle='white'; cctx.fillRect(p.x,p.y,1,1); } // duplicate the top half of each canvas // onto the bottom half of the same canvas for(var p=0;p<panelCount;p++){ panels[p].ctx.drawImage(panels[p].canvas,0,ch); } // begin animating drawStartTime=performance.now(); requestAnimationFrame(animate); function draw(time){ ctx.clearRect(0,0,cw,ch); for(var i=0;i<panels.length;i++){ var panel=panels[i]; ctx.drawImage(panel.canvas,panel.offsetX,panel.offsetY); } } function animate(time){ for(var i=0;i<panels.length;i++){ var p=panels[i]; p.offsetX+=p.driftrate; if(p.offsetX<minOffsetX || p.offsetX>maxOffsetX){ p.driftrate*=-1; p.offsetX+=p.driftrate; } p.offsetY+=p.fallrate; if(p.offsetY>=0){p.offsetY=-ch;} draw(time); } requestAnimationFrame(animate); }
body{ background-color:#6b92b9; padding:10px; } #canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>