I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches.
The first fiddle: /
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 /, 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: /
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
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?
I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches.
The first fiddle: http://jsfiddle/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./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/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./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?
Share Improve this question edited Aug 30, 2022 at 12:13 Yves M. 31.1k24 gold badges109 silver badges149 bronze badges asked Dec 24, 2014 at 0:54 SirSir 8,27717 gold badges88 silver badges154 bronze badges 8-
1
I could be wrong, but creating 1000
canvas
elements doesn't look right to me. Shoulddocument.createElement('canvas')
be run 1000 times? – James Taylor Commented Dec 24, 2014 at 0:57 - @JamesTaylor well that was how I interpreted html5Rock's example..i might of misunderstood? – Sir Commented Dec 24, 2014 at 0:58
- If you pool and create like 15 kinds of particules and then only use 15 canvas to pre-render, this would make sens - otherwise, don't prerender – topheman Commented Dec 24, 2014 at 1:02
- @topheman so pre-render is only in certain situations? – Sir Commented Dec 24, 2014 at 1:03
- 1 no, but in the html5rocks example, they prerender 1 image on a canvas and then reuse this very same one as a pointer to create their scene. You should do the same kind of thing (I doubt you need 10 000 kinds of particules, if only the radius changes between 1 and 4 for example) The pitfall with your example is that you change the color randomally, so it will be difficult to unify particules – topheman Commented Dec 24, 2014 at 1:08
3 Answers
Reset to default 5Best 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>
I don't think you want to create a new canvas
element every time. Doing so causes a huge performance drain.
When I moved this code out of the for loop, the performance instantly improved. I think doing so will allow you to optimize your code to achieve the intended behavior:
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);
Check out this revised JSFiddle.
Hope this helped!
You would pre-render elements you paint many times.
Say for example you have a landscape where you paint bushes (of same form) on various locations during a game scroll. The the use of memory canvas would be ok.
For your code you should try to divide your flakes into for example 10 sizes. Thus create 10 memory canvases. Then paint these into random possitions.
In other words you copy 10 canvases 1.000 times. Not 10.000 canvases 10.000 times.