Slow performance drawing dashed lines in HTML5 Canvas - javascript

I am attempting to make a Pong clone using HTML5 canvas. I want to draw a dashed line down the middle of the playing field as is found in the original Pong. I am doing this by extending the CanvasRenderingContext2D object as shown in David Geary's excellent book:
CanvasRenderingContext2D.prototype.dashedLine = function (x1, y1, x2, y2, dashLength) {
dashLength = dashLength === undefined ? 5 : dashLength;
var deltaX = x2 - x1;
var deltaY = y2 - y1;
var numDashes = Math.floor(
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength);
for (var i=0; i < numDashes; ++i) {
context[ i % 2 === 0 ? 'moveTo' : 'lineTo' ]
(x1 + (deltaX / numDashes) * i, y1 + (deltaY / numDashes) * i);
}
I then have a render() function that actually makes all the calls to render elements on the canvas. Included in this is my renderBackground() function which colors the background and draws the dashed line:
function render() {
ctx.clearRect(0, 0, cWidth, cHeight);
renderBackground();
// Rest removed for brevity
}
function renderBackground() {
ctx.lineWidth = 5;
ctx.strokeStyle = '#FFFFFF';
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, cWidth, cHeight);
ctx.dashedLine(0, 0, 0, cHeight, 10);
ctx.stroke()
}
Then at the end I have a function called animLoop() that actually calls the render() function and makes use of requestAnimationFrame() for smoother animations:
function animLoop() {
render();
requestAnimationFrame(animLoop);
}
window.requestAnimationFrame = (function() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
}
);
})();
If I let my game run for more than 30 seconds it starts slowing down dramatically to the point that it is unplayable and CPU usage by the browser hovers around 134% for both Firefox and Chrome. The slowness is only present when I am rendering the dashed line. I am not sure what is going, but below I also ran my code through Chrome Inspectors profiler and get the following:
My renderBackground() function is only taking .46% of the CPU time. Also I am not sure what the (program) is supposed to signify. Any thoughts on what could be causing the slowness?
Also you can see the complete code I have so far on my Github repo.

You are accumulating all calls of lineTo on the default path each time ctx.dashedLine is called and call stroke will stroke all lines in path since the application start. Because you are running an animation, quickly the path will have a LOT of lines to draw when stroke is called each frame.
Add ctx.beginPath() before ctx.dashedLine to solve the problem.
function renderBackground() {
ctx.lineWidth = 5;
ctx.strokeStyle = '#FFFFFF';
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, cWidth, cHeight);
ctx.beginPath(); // <-- add it here
ctx.dashedLine(0, 0, 0, cHeight, 10);
ctx.stroke();
}
When drawing using a path, you are using a virtual "pen" or "pointer". So you'll create a virtual path with begin path, draw the lines and finally stroke that lines. In next frame you'll begin a new virtual path, draw the new lines in the path and stroke again. This way the performance stays stable.
Demo

Related

Render canvas faster than 60 times per second?

My JS code:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var mouse = {x:0,y:0}
const times = [];
let fps;
function refreshLoop() {
window.requestAnimationFrame(() => {
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
refreshLoop();
});
}
refreshLoop();
function draw() {
ctx.fillStyle = "black"
ctx.fillRect(0, 0, c.width, c.height);
ctx.strokeStyle = "white"
ctx.beginPath();
var e = window.event;
ctx.arc(mouse.x, mouse.y, 40, 0, 2*Math.PI);
ctx.stroke();
ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText(fps, c.width/2, c.height/2);
}
setInterval(draw, 0);
document.addEventListener('mousemove', function(event){
mouse = { x: event.clientX, y: event.clientY }
})
My HTML is just the canvas declaration.
To my understanding, setinterval(x, 0) is supposed to run as fast as possible but it's never exceeding 60fps. I'm trying to hit 240+ fps to reduce input lag.
First, never use setInterval(fn, lessThan10). There is a great possibility that fn will take more than this time to execute, and you may end up stacking a lot of fn calls with no interval at all, which can result* in the same as the well known while(true) browser crasher®.
*Ok, in correct implementations, that shouldn't happen, but you know...
Now, to your question...
Your code is quite flawn.
You are actually running two different loops concurrently, which will not be called at the same interval.
You are checking the fps in a requestAnimationFrame loop, which will be set at the same frequency than your Browser's painting rate (generally 60*fps*).
You are drawing in the setInterval(fn, 0)
Your two loops are not linked and thus, what you are measuring in the first one is not the rate at which your draw is called.
It's a bit like if you did
setInterval(checkRate, 16.6);
setInterval(thefuncIWantToMeasure, 0);
Obviously, your checkRate will not measure thefuncIWantToMeasure correctly
So just to show that a setTimeout(fn, 0) loop will fire at higher rate:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var mouse = {
x: 0,
y: 0
}
const times = [];
let fps;
draw();
function draw() {
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
ctx.fillStyle = "black"
ctx.fillRect(0, 0, c.width, c.height);
ctx.strokeStyle = "white"
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 40, 0, 2 * Math.PI);
ctx.stroke();
ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText(fps, c.width / 2, c.height / 2);
setTimeout(draw, 0);
}
<canvas id="myCanvas"></canvas>
Now, even if a nested setTimeout loop is better than setInterval, what you are doing is a visual animation.
It makes no sense to draw this visual animation faster than the browser's painting rate, because what you will have drawn on this canvas won't be painted to screen.
And as said previously, that's exactly the rate at which an requestAnimationFrame loop will fire. So use this method for all your visual animations (At least if it has to be painted to screen, for some rare case there are other methods I could link you to in comments if needed).
Now to solve your actual problem, which is not to render at higher rate, but to handle user's inputs at such rate, then the solution is to split your code.
Keep your drawing part bound to a requestAniamtionFrame loop, doesn't need to get faster.
Update your object's values that should respond to user's gesture synchronously from user's input. Though, beware some user's gestures actually fire at very high rate (e.g WheelEvent, or window's resize Event). Generally, you don't need to get all the values of such events, so you might want to bind these in rAF throttlers instead.
If you need to do collision detection with moving objects, then perform the Math that will update moving objects from inside the user's gesture, but don't draw it on screen.

Canvas animation keeps redrawing previous frames

I am figuring this one out an unhealthy amount of time for now and I did not found any note for this bug. I started to build a simple HTML Canvas animation in JavaScript. For now I expect the small squares to move. Here is the code (I am also using babeljs):
class Pod {
constructor(x,y) {
this.posX = x;
this.posY = y;
this.velX = getRandomNumber(-5,5);
this.velY = getRandomNumber(-5,5);
}
getPos() {
return ([this.posX,this.posY]);
}
move() {
this.posX += this.velX;
this.posY += this.velY;
}
render() {
ctx.save();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
}
}
/*the classes end here*/
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var elementsNum = 10;
const stack = new Array(elementsNum);
for(var i = 0; i < elementsNum; i++) {
stack[i] = new Pod(getRandomNumber(0,500),getRandomNumber(0,500));
}
function run() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx = canvas.getContext('2d');
for(var i = 0; i < elementsNum; i++) {
stack[i].move();
stack[i].render();
}
//window.requestAnimationFrame(run);
}
/*helper functions*/
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
After running a cycle of the function run(), the small squares (I called them Pods) are rendered. Next cycle starts with clearing the canvas with ctx.clearRect... I am resetting the context and start moving and then drawing the Pods from the stack. When I draw the first Pod, it will draw all of them and also the previous frame.
Here is the codepen for this particular script: http://codepen.io/erikputz/pen/YNNXqX
(I knowingly commented the requestAnimationFrame, so you need to use the console to call the run() function)
Thank you forward for your help.
http://codepen.io/zfrisch/pen/bgazyO
This should solve your issue:
render() {
ctx.beginPath();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
}
With canvas you need to identify individual shapes through code by using the beginPath and closePath methods. In certain methods this is innate, like in fillRect. Hence the above code could be simplified even more to:
render() {
ctx.fillStyle = "#ffffff";
ctx.fillRect(this.posX,this.posY,3,3);
}
When you're just declaring a shape (rect) you need to specify when the path begins and when it is closed, otherwise it will most likely cause issues like the shape-bleeding you had in your original code.
Also, as a tip, you don't need to save state.save() / .restore() unless you're translating/scaling/rotating/or moving on the canvas element. Filling shapes doesn't apply.

Growing infinite circles with JS and Canvas

I want to make a simple page that grows circles from its center ad infinitum. I'm almost there, but I can't figure out how to repeatedly grow them (resetting the radius i to 0 at a certain interval and calling the function again). I assume it will require a closure and some recursion, but I can't figure it out.
// Initialize canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.getElementsByTagName('body')[0].appendChild(canvas);
// Grow a circle
var i = 0;
var draw = function() {
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, i, 0, 2 * Math.PI);
ctx.fill();
i += 4;
window.requestAnimationFrame(draw);
}
draw();
Two things I'd do...
First, modify your draw function so that if the circle gets to a certain size, the i variable is reset back to zero. That starts the circle over again.
Second, add a setInterval timer to call your draw function at some time interval. See http://www.w3schools.com/js/js_timing.asp for details.
This setup will cause draw() to be called regularly, and the reset of i to zero makes it repeat.
So this did indeed require a closure. We wrap the initial function in a closure, and call it's wrapper function, which reinitializes I every time when called. draw() grows a single circle, and drawIt()() starts a new circle.
var drawIt = function(color) {
var i = 0;
return function draw() {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, i, 0, 2 * Math.PI);
ctx.fill();
i+=1*growthFactor;
// Growing circles until they are huge
if (i < canvas.width) {
window.requestAnimationFrame(draw);
if (i === spacing) {
circles++
drawIt(nextColor())();
}
}
}
};
drawIt(nextColor())();
})();

How can I execute different animations one by one within a HTML5 Canvas context

I am working to create an animation which draws a canvas like "O - O" shape.
The animation should first animate to draw the circle on the left, then the right circle and finally the connection in between.
I am able to draw a circle but I would like to know how can I draw the three elements one by one instead of drawing three of them together.
Psuedo-code:
window.onload = draw;
function draw(){
drawcircle();
}
function drawcircle(){
draw part of circle
if( finish drawing){
clearTimeout
}
else{
setTimeout(drawcircle());
}
However, if I run another drawcircle function after first in the draw() function. Both circles are drawn at the same time instead of one by one. Is there any method to draw each elements one by one?
Thanks very much
What you want are callbacks:
Circle(ctx, 50, 50, 25, 1000, function () { // animate drawing a circle
Circle(ctx, 150, 50, 25, 1000, function () { // then animate drawing a circle
Line(ctx, 75, 50, 125, 50, 1000, function(){ // then animate drawing a line
alert('done');
});
});
});
Here's a simple implementation of animated drawings of circles and lines:
function Circle(context, x, y, radius, dur, callback) {
var start = new Date().getTime(),
end = start + dur,
cur = 0;
(function draw() {
var now = new Date().getTime(),
next = 2 * Math.PI * (now-start)/dur;
ctx.beginPath();
ctx.arc(x, y, radius, cur, next);
cur = Math.floor(next*100)/100; // helps to prevent gaps
ctx.stroke();
if (cur < 2 * Math.PI) requestAnimationFrame(draw); // use a shim where applicable
else if (typeof callback === "function") callback();
})();
}
function Line(context, x1, y1, x2, y2, dur, callback) {
var start = new Date().getTime(),
end = start + dur,
dis = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)),
ang = Math.atan2(y2-y1, x2-x1),
cur = 0;
(function draw() {
var now = new Date().getTime(),
next = Math.min(dis * (now-start)/dur, dis);
ctx.beginPath();
ctx.moveTo(x1 + Math.cos(ang) * cur, y1 + Math.sin(ang) * cur);
ctx.lineTo(x1 + Math.cos(ang) * next, y1 + Math.sin(ang) * next);
cur = next;
ctx.closePath();
ctx.stroke();
if (cur < dis) requestAnimationFrame(draw); // use a shim where applicable.
else if (typeof callback === "function") callback();
})();
}
And here's a working (webkit only) demo: http://jsfiddle.net/QSAyw/3/
What you propabably really want to be using is requestAnimationFrame. You can then completely leave out the setTimeout. http://paulirish.com/2011/requestanimationframe-for-smart-animating/ is a great blog post that'll help you get started.

Making a circle that moves

Been trying to get something to work in HTML and I haven't been able to nail it down quite yet. Basically, I want to create a canvas and then make a circle inside the canvas that moves from edge of the canvas to edge of the canvas. Any suggestions?
EDIT:
Folks wanted what I have so far, so here it is:
<html>
<head>
<script type="text/javascript">
function draw () {
var canvas = document.getElementById('circle');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.fillStyle = "rgb(150,29,28)";
var startPoint = (Math.PI/180)*0;
var endPoint = (Math.PI/180)*360;
context.beginPath();
context.arc(200,200,150,startPoint,endPoint,true);
context.fill();
context.closePath();
}
}
}
</script>
</head>
<body onload="init();">
<canvas id="canvas" width="500" height="500"></canvas><br>
</body>
</html>
I'm not really sure how to get a circle onto the canvas (and I'm still shakey on the implementation thereof) as well as how to make it, y'know, move. I have examples of how to rotate something, but not really how to move it. Sorry for the inexperience guys, trying to learn HTML on my own, but the book I've got doesn't seem really descriptive on this aspect, even though it's a supposed to be teaching me HTML.
So up to now you have a code where you're able to draw a circle at a certain position onto the canvas surface, very well, now in order to make it look like it's moving you'll have to keep drawing it again and again with it's position slightly changed to give a smooth motion effect to it, it's a standard to draw things 60 times per second (I think that's because 60 frames per second is the most that the human eye can notice, or whatever). And of course, each time you draw it on another position, it's going to be necessary to clear the older drawing.
Let's change your code just a bit in order to make it animation-friendly:
<script type="text/javascript">
function init()
{
canvas = document.getElementById('canvas');
if(canvas.getContext)
context = canvas.getContext('2d');
else return;
setInterval(draw, 1000 / 60); // 60 times per second
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
// var startPoint = (Math.PI/180)*0; Kinda redundant, it's just 0
// var endPoint = (Math.PI/180)*360; Again, it's just PI times 2
context.beginPath();
context.arc(200, 200, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
</script>
</head>
<body onload="init();">
<canvas id="canvas" width="500" height="500"></canvas><br>
Now there are lots of funny ways to make an object move towards a fixed point, but the simplest is, of course, moving along a straight line. To do so you'll need
A vector containing the object's current position
A vector containing the object's target position
Let's change your code so we have those at hand
var canvas, context,
position = {x: 200, y: 200},
target = {x: 400, y: 400};
function init()
{
...
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
context.beginPath();
context.arc(position.x, position.y, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
Good! This way whenever you change one of the values in the position object, the position of your circle is going to be affected.
If you subtract the current position from the target position you'll get another vector which points straight to the target position coming from the current position right? So get that vector and just normalize it, turning it's length to 1, the result will actually be the (cosine, sine) of the angle from the target to the object's position. What that means is if you add that normalized vector to the object's current position, the object will move towards the target position 1 unit at a time.
Normlizing a vector is simply dividing it's components by it's length.
function normalize(v)
{
var length = Math.sqrt(v.x * v.x + v.y * v.y);
return {x: v.x / length, y: v.y / length};
}
var step = normalize({x: target.x - position.x, y: target.y - position.y});
Ok, now all we need to do is keep adding the step vector to the object's current position until it reaches the target position.
function normalize(v)
{
var length = Math.sqrt(v.x * v.x + v.y * v.y);
return {x: v.x / length, y: v.y / length};
}
var canvas, context,
position = {x: 200, y: 200},
target = {x: 400, y: 400},
step = normalize({x: target.x - position.x, y: target.y - position.y});
function init()
{
canvas = document.getElementById('canvas');
if(canvas.getContext)
context = canvas.getContext('2d');
else return;
setInterval(draw, 1000 / 60);
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
context.beginPath();
context.arc(position.x, position.y, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
position.x += step.x;
position.y += step.y;
}
And there you have it. Of course it's pretty basic code, you'll have to add a codition to check whether or not the object has arrived to the target otherwise it will just keep going past the target. If it's moving too slow, just scale the step vector by an arbitrary speed factor. Also in a real world app you'd have lot's of objects and not only just a circle so you'd have to make it entirely object-oriented since every object would have it's position, target position, color etc etc etc. A library to work with vectors would come in handy. This is one I wrote for myself some time ago: http://pastebin.com/Hdxg8dxn
Here you go, give this a crack -
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
function toggleClass(element, newStr)
{
index=element.className.indexOf(newStr);
if ( index == -1)
element.className += ' '+newStr;
else
{
if (index != 0)
newStr = ' '+newStr;
element.className = element.className.replace(newStr, '');
}
}
function forEachNode(nodeList, func)
{
var i, n = nodeList.length;
for (i=0; i<n; i++)
{
func(nodeList[i], i, nodeList);
}
}
window.addEventListener('load', mInit, false);
var canvas, hdc;
var posx=50, posy=0, radius=50;
function circle(hdc, x, y, radius)
{
hdc.beginPath();
hdc.arc(x, y, radius, 0, 2*Math.PI);
hdc.stroke();
//arc(x,y,r,start,stop)
}
function mInit()
{
canvas = byId('tgtCanvas');
hdc = canvas.getContext('2d');
//circle(hdc, posx, posy, 50);
setInterval(animateStep, 50);
}
var velX = 2;
function animateStep()
{
posx += velX;
if (posx+radius > canvas.width)
velX *= -1;
else if (posx-radius < 0)
velX *= -1;
hdc.clearRect(0,0,canvas.width,canvas.height);
circle(hdc, posx, posy, radius);
}
</script>
<style>
</style>
</head>
<body>
<canvas id='tgtCanvas' width='256' height='256'></canvas>
</body>
</html>

Categories