• Ideas on how to improve efficiency of canvas?
    7 replies, posted
Hi all. I used to do a lot of coding, but haven't touched it in at least 8 years and have basically had to relearn everything, even really basic things. I've just been messing about in my own time, trying to create something to display orbits of planets and stuff like that. I've been Googling a lot for advice on a way to remove an image from a canvas without having to remove pixels manually, but that does not seem to exist due to the way canvas draws them in the first place, meaning I am having to run the trigonometry over and over, making performance really bad indeed. It could be argued that canvas is maybe poor for graphics, but I am unsure as I've never really done any web development stuff. One way I know that I can optimise is to only remove the pixels in the area of the image each time, working out where that is in the circle and removing just that bit whilst redrawing only that area of the circle. Still though, I can't imagine this will save me enough time to be able to make this a relatively smooth animation. Any ideas? URL:www.danieldeanhodge.co.uk PS: I use a global variable, lol. I imagine that's as taboo in JS as it is in C++, but I haven't been taking it massively seriously at this stage. I'm just optimising as I go along. Any help would be much appreciated! <html> <header> <script> var globalAngle=0; function moveImage(imageID, x, y) { var imgMercury=document.getElementById(imageID); canvasContext.drawImage(imgMercury,x,y); } function drawCircle(centreCanvasX, centreCanvasY, radius, canvasContext) { for(nAngle = 0;nAngle <= 360;nAngle++) { var currentX = centreCanvasX + radius * Math.cos(nAngle * (Math.PI / 180)) var currentY = centreCanvasY + radius * Math.sin(nAngle * (Math.PI / 180)) var nextX = centreCanvasX + radius * Math.cos((nAngle+1) * (Math.PI / 180)) var nextY = centreCanvasY + radius * Math.sin((nAngle+1) * (Math.PI / 180)) canvasContext.moveTo(currentX, currentY) canvasContext.lineTo(nextX,nextY); canvasContext.stroke(); } } function movePlanet(centreCanvasX, centreCanvasY, radius, planetID, nAngle) { var currentX = centreCanvasX + radius * Math.cos(nAngle * (Math.PI / 180)) var currentY = centreCanvasY + radius * Math.sin(nAngle * (Math.PI / 180)) moveImage(planetID, currentX, currentY); } function iterateOrbits(canvasContext) { canvasContext.fillStyle="red"; canvasContext.fillRect(0,0,820,820); canvasContext.clearRect(0,0,820,820); drawCircle(410, 410, 400, canvasContext); movePlanet(410 - 32, 410 - 38, 400, "#mercury", globalAngle) globalAngle += 17; } </script> </header> <body> <center> <canvas id="solarSystem" width="820" height="820" style="border:1px solid #000000;"> <script> var canvasElement = document.getElementById("solarSystem"); var canvasContext = canvasElement.getContext("2d"); </script> </canvas> <form> <input type="button" value="Iterate" onClick="iterateOrbits(canvasContext);"/> </form> <img src="mercury.png" id="#mercury" width="50" height="50"> </center> </body> </html>
If I were you I'd have the circle drawn on a secret canvas, and then the planet saves its location every time it moves and then when it moves the canvas just draws the part of the circle inside a rectangle where your planet used to be. Not sure why your canvas gets slower every iteration.
You're calling canvasContext.stroke() 360 times per circle, every time you do that it will send off 1/360 line segments to the gpu, and then it has to wait for that draw command to complete before it can draw the next segment. Try calling the stroke function once per object or less, call beginPath before you start the loop, and after the loop finish off with a single stroke call. It can exist out of any number of segments not just 1.
Ah, I had no idea it worked like that! I've tried that idea on my local environment and it's massively sped up now. Thanks very much. I'm going to try this too. It seems as though the difference from the first part probably means I won't have to yet though.
Why are you drawing the circle that way? You can draw it using ctx.arc(): https://www.w3schools.com/tags/canvas_arc.asp
I've never used canvas before, so this is all a learning experience for me. I actually have another question regarding all of this though. I am trying to use the setInterval function so it'll not require a button push to iterate the series, but for some reason setInterval doesn't seem to get on well with canvas, or at least that seems to be the case, as using setInterval to prompt an alert box or something works fine. Do you have any ideas as to why that is?
Looking at the code on your site, you've got: setInterval(iterateOrbits(canvasContext), 200); setInterval takes a function as the first argument. What you have there, iterateOrbits(canvasContext), is a function that is being evaluated. If you look at the body of the function iterateOrbits: function iterateOrbits(canvasContext) { canvasContext.fillStyle="red"; canvasContext.clearRect(0,0,820,820); drawCircle(410, 410, 400, canvasContext, 0, 360); canvasContext.stroke(); movePlanet(410 - 32, 410 - 38, 400, "#mercury", globalAngle, canvasContext) globalAngle += 17; }  You see that you have a function that does many things, but returns nothing. By default, functions with no return statement in them will return undefined. So what you're passing in to setInterval is not the function called "iterateOrbits" (with the parameter canvasContext), you are passing in the result of calling that function (which is undefined). So, you need to wrap that function call in another function, like this: setInterval(function(){ iterateOrbits(canvasContext) }, 200); so what you're passing here is a function (with no name - an anonymous function) that calls your other function, iterateOrbits
setInterval(iterateOrbits, 200, canvasContext); That should do the same thing. No need to make it a totally separate function.
Sorry, you need to Log In to post a reply to this thread.