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())();
})();
Related
When creating an HTML canvas I was planning on making these cylinders and animating marbles moving inside them. However, when trying to do so it would just delete everything. After messing around with my code, I discovered the problem was due to the fillStyle which was a CanvasPattern from an image.
This snippet simulates exactly what I am experiencing. The rectangle draws perfectly fine, however, after 1 second, when the interval runs, it disappears and there is no arc or "marble" drawn. There are no errors in console either
With Interval (Not working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
When I get rid of the interval it would work, but when the interval is there, nothing is drawn.
I have absolutely no idea why this is happening and I cannot find anything on the internet regarding this problem. Is there a way I can animate this marble while having the image continue to mask its fillStyle??
Without Interval (Working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
//var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
//}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
Things I've tried:
Got rid of beginPath and closePath, doesn't make anything disappear but doesn't display arc
Recreating pattern inside the interval
Making the fillstyle a colour for everything (Works)
Making the fillstyle of the marble a colour (Doesnt work)
EDIT: After looking some more, I believe the problem is in the globalCompositeOperation. It's what deals with the pattern intersecting the drawing. When looking at all the types, source-in is the only one that satisfies my expected result, however, it's not working in this situation weirdly.
Thank you in advance
The problem is your ctx.globalCompositeOperation instruction. Using source-in, you're explicitly telling the canvas to make anything that's a different color from the new thing you're drawing (on a per pixel basis) transparent. Since every pixel is different, everything becomes transparent and you're left with what looks like an empty canvas (even if the ImageData will show RGBA data in which the RGB channels have meaningful content, but A is 0).
Remove the globalCompositeOperation rule and you're good to go, but you should probably take some time to rewrite the logic here, so that nothing happens until your image is loaded, because your code is pretty dependent on that pattern existing: wait for the image to load, the build the pattern, assign it to the context, and then start your draw loop.
const canv = document.getElementById("canvas");
const ctx = canv.getContext('2d');
let x = 60, y = 20;
function start() {
const matte = new Image(canv.width, canv.height);
matte.addEventListener(`load`, evt =>
startDrawing(ctx.createPattern(matte, 'repeat'))
);
matte.addEventListener(`load`, evt =>
console.error(`Could not load ${matte.src}...`);
);
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
}
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
setInterval(() => {
draw();
y += 10;
}, 1 * 1000);
}
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// and kick everything off
start();
Although on another note, normally setInterval is not the best choice for animations: you usually want requestAnimationFrame instead, with a "wall time" check (e.g. doing things depending on the actual clock, instead of trusting intervals):
...
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
startAnimation();
}
let playing, lastTime;
function startAnimation() {
playing = true;
lastTime = Date.now();
requestAnimationFrame(nextFrame);
}
function stopAnimation() {
playing = false;
}
function nextFrame() {
let newTime = Date.now();
if (newTime - lastTime >= 1000) {
draw();
}
if (playing) {
lastTime = newTime;
requestAnimationFrame(nextFrame);
}
}
...
https://jsbin.com/wawecedeve/edit?js,output
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.
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.
Seems like there are other questions like this and I'd like to avoid a buffer and/or requestAnimationFrame().
In a recent project the player is flickering but I cannot find out the reason. You can find the project on JSFiddle: https://jsfiddle.net/90wjetLa/
function gameEngine() {
timer += 1;
timer = Math.round(timer);
// NEWSHOOT?
player.canShoot -= 1;
// MOVE:
movePlayer();
shootEngine(); // Schussbewegung & Treffer-Abfrage
// DRAW:
ctx.beginPath();
canvas.width = canvas.width;
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
drawField();
drawPlayer();
drawShoots();
setTimeout(gameEngine, 1000 / 30);
}
Each time you write to a visible canvas the browser want's to update the display. Your drawing routines might be out of sync with the browsers display update. The requestAnimationFrame function allows you to run all your drawing routines before the display refreshes. Your other friend is using an invisible buffer canvas. Draw everything to the buffer canvas and then draw the buffer to the visible canvas. The gameEngine function should only run once per frame and if it runs multiple times you could see flicker. Try the following to clear multiple runs in the same frame.
(edit): You might also want to clear the canvas instead of setting width.
(edit2): You can combine the clearRect, rect, and fill to one command ctx.fillRect(0, 0, canvas.width, canvas.height);.
var gameEngineTimeout = null;
function gameEngine() {
// clear pending gameEngine timeout if it exists.
clearTimeout(gameEngineTimeout);
timer += 1;
timer = Math.round(timer);
// NEWSHOOT?
player.canShoot -= 1;
// MOVE:
movePlayer();
shootEngine(); // Schussbewegung & Treffer-Abfrage
// DRAW:
ctx.beginPath();
//canvas.width = canvas.width;
//ctx.clearRect(0, 0, canvas.width, canvas.height);
//ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
//ctx.fill();
drawField();
drawPlayer();
drawShoots();
gameEngineTimeout = setTimeout(gameEngine, 1000 / 30);
}
i am trying to animate 3 different shapes with setTimeout , my question is how can i use multiple setTimeout to animate 3 different shapes also is there a better way to do this maybe using setInterval
window.onload = draw;
var x = 5;
var y = 5;
radius = 50;
var x2 = 50;
var y2 = 120;
var x3 = 53;
var y3 = 230;
var context;
var loopTimer;
function draw(){
var canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
context.save();
context.clearRect(0,0,720,550);
rectangle(x,y);
circle(x2,y2);
circle2(x3,y3);
}
function rectangle(x,y){
//drawing a rectangle
context.fillStyle = "rgba(93,119,188,237)";
context.clearRect(0,0,720,550);
context.rect(x, y, 50, 50);
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'yellow';
context.stroke();
x += 1;
loopTimer = setTimeout('rectangle('+x+','+y+')',50);
}
function circle(x2,y2){
//darwong a circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = "#0000ff";
//Draw a circle of radius 20 at the current coordinates on the canvas.
context.arc(x2, y2, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
x2 += 1;
loopTimer = setTimeout('circle('+x2+','+y2+')',20);
}
function circle2(x3,y3){
//drawing a second circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = 'green';
context.arc(x3, y3, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
context.lineWidth = 5;//border around the circle
context.strokeStyle = 'red';
context.stroke();
x3 += 1;
loopTimer = setTimeout('circle2('+x3+','+y3+')',20);
}
Animating objects
When doing digital animation there is never need for more than one single timer.
The key is to bind properties to the objects being animation such as its position, speed (or steps), color, shape and so forth.
The logic step therefor is to create custom objects that we can collect this information and use an update function to do all the updates for us in a single step within the loop.
Example
ONLINE DEMO HERE
Lets create an object collection where we store all our objects:
var animObjects = [];
Nothing fancy about that - just an array.
A single loop
To show how simple this can get I will show the loop itself first, then dissect the object. The loop simply iterates through our collection and calls the update method on each object:
function loop() {
/// clear canvas before redrawing all objects
ctx.clearRect(0, 0, demo.width, demo.height);
/// loop through the object collection and update each object
for(var i = 0, animObject; animObject = animObjects[i]; i++) {
animObject.update();
}
/// use this instead of setTimeout/setInterval (!)
requestAnimationFrame(loop);
}
Now, you noticed probably that we used requestAnimationFrame (rAF) here instead of setTimeout or setInterval. rAF allows us to synchronize to the monitor's update frequency whereas setTimout/setInterval cannot. In addition rAF works more efficient than the other two which will benefit us if we need to animate a lot of stuff.
The animation object
Now lets take a look at the object, how come we only need to call update and things animate?
As we saw earlier we create a animated circle object simply by calling:
var animObject = new animCircle(context, x, y, radius, color, stepX, stepY);
This allows us to set which context we want to use (in case we use several layers of canvas), the start position, color and number of steps per frame. Note that these properties can be changed during the animation (e.g. change radius and color).
The object itself looks like this -
function animCircle(ctx, x, y, r, color, stepX, stepY) {
/// keep a reference to 'this' context for external calls
var me = this;
/// make the parameters public so we can alter them
this.x = x;
this.y = y;
this.radius = r;
this.color = color;
this.stepX = stepX;
this.stepY = stepY;
/// this will update the object by incrementing x and y
this.update = function() {
me.x += me.stepX;
me.y += me.stepY;
/// additional updates can be inserted here
render();
}
/// and this takes care of drawing the object with current settings
function render() {
ctx.beginPath();
ctx.arc(me.x, me.y, me.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = me.color;
ctx.fill();
}
return this;
}
That's all there is to it!
The objects update() method will do all the calculations for us as well as call the internal render() method.
You can create many of these objects at various positions and speeds and animate all of them from a single loop.
I only created an object for the circle to keep things simple. You should be able to create an object for rectangle and what have you by using this as a base. You can of course extent the object to keep track of other things as well such as stroke color, angles and so forth.
I also made the objects bounce off the canvas' edges for the sake of demo. Please adjust as needed.
If i get the question right... why you just dont create three different functions like drawCircle? drawWhatever and create three setTimeouts? or something like that... ?
why you use so manny ' ? there is no reason to do that in:
var loopTimer = setTimeout('draw('+x+','+y+')',20);