creating a recurring animation that doesn't interrupt previous call - javascript

I'm trying to creating a recurring animation of vertical rows of circles. Each row begins at the bottom of the browser and goes to the top, occurring at random intervals between 0 and 2 seconds. The problem is that when the animations are too close together in timing, the new one discontinues the previous one, so sometimes the row of circles does not go all the way to the top of the browser. How can I prevent this and instead have multiple rows animating at once?
Here's the fiddle
var timer;
var newLine1 = function(){
clearInterval(timer);
var posX = Math.random() * canvas.width;
posY = canvas.height;
timer = setInterval(function() {
posY -= 20;
c.fillStyle = "rgba(23, 23, 23, 0.05)";
c.fillRect(0, 0, canvas.width, canvas.height);
c.fillStyle = "white";
c.beginPath();
c.arc(posX, posY, 10, 0, twoPi, false);
c.fill();
}, 30);
setTimeout(newLine1, Math.random() * 2000);
};
newLine1();

Here's one way using a rising image of your vertically fading circles and the preferred animation loop (requestAnimationFrame).
Annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// cache PI*2 because it's used often
var PI2=Math.PI*2;
// variables related to the stream of circles
var radius=10;
var alpha=1.00;
var alphaFade=0.05;
// create an in-memory canvas containing
// a stream of vertical circles
// with alpha=1.00 at top and alpha=0.00 at bottom
// This canvas is drawImage'd on the on-screen canvas
// to simulate animating circles
var streamHeight=(1/alphaFade)*radius*2;
var columnCanvas=document.createElement('canvas');
var columnCtx=columnCanvas.getContext('2d');
columnCanvas.width=radius*2;
columnCanvas.height=streamHeight;
for(var y=radius;y<ch+radius*2;y+=radius*2){
columnCtx.fillStyle='rgba(255,255,255,'+alpha+')';
columnCtx.beginPath();
columnCtx.arc(radius,y,radius,0,PI2);
columnCtx.closePath();
columnCtx.fill();
alpha-=alphaFade;
}
// just testing, remove this
document.body.appendChild(columnCanvas);
// create an array of stream objects
// each stream-object is a column of circles
// at random X and with Y
// animating from bottom to top of canvas
var streams=[];
// add a new stream
function addStream(){
// reuse any existing stream that's already
// scrolled off the top of the canvas
var reuseStream=-1;
for(var i=0;i<streams.length;i++){
if(streams[i].y<-streamHeight){
reuseStream=i;
break;
}
}
// create a new stream object with random X
// and Y is off-the bottom of the canvas
var newStream={
x: Math.random()*(cw-radius),
y: ch+radius+1
}
if(reuseStream<0){
// add a new stream to the array if no stream was available
streams.push(newStream);
}else{
// otherwise reuse the stream object at streams[reuseStream]
streams[reuseStream]=newStream;
}
}
// move all streams upward if the
// elapsed time is greater than nextMoveTime
var nextMoveTime=0;
// move all streams every 16ms X 3 = 48ms
var moveInterval=16*3;
// add a new stream if the
// elapsed time is greater than nextAddStreamTime
var nextAddStreamTime=0;
// set the background color of the canvas to black
ctx.fillStyle='black';
// start the animation frame
requestAnimationFrame(animate);
// the animation frame
function animate(currentTime){
// request a new animate loop
requestAnimationFrame(animate);
// add a new stream if the
// current elapsed time is > nextAddStreamTime
if(currentTime>nextAddStreamTime){
nextAddStreamTime+=Math.random()*1000;
addStream();
}
// move all streams upward if the
// current elapsed time is > nextMoveTime
if(currentTime>nextMoveTime){
nextMoveTime+=moveInterval;
ctx.fillRect(0,0,cw,ch);
for(var i=0;i<streams.length;i++){
var s=streams[i];
if(s.y<-streamHeight){continue;}
ctx.drawImage(columnCanvas,s.x,s.y);
s.y-=radius*2;
}
}
}
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=400></canvas>

How about like this. I've only re-worked the newLine1() function. At the end of the line drawing (when posY is < 0), then you trigger the timeout to start another line.
var newLine1 = function(){
var posX = Math.random() * canvas.width;
posY = canvas.height;
timer = setInterval(function() {
posY -= 20;
... drawing code ...
if(posY < 0) {
clearInterval(timer);
setTimeout(newLine1, Math.random() * 2000);
}
}, 30);
};
newLine1();
It also means you don't need the clearInterval at the top, but you clear the timer interval only when the drawing is done (in the posY<0 case).
UPDATE:
Here is take two. I've factored out the line rendering stuff into it's own little class. renderLine then just manages the line intervals and the restarting a line (with timeout) when the first render is done. Finally, we just kick off a bunch of lines.
window.onload = function() {
function LineAnimator(canvas, startX, startY) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.startX = startX;
this.startY = startY;
this.interval = null;
this.reset();
}
LineAnimator.prototype.reset = function() {
this.posX = 0 + this.startX;
this.posY = 0 + this.startY;
}
/** return false when there's no more to draw */
LineAnimator.prototype.render = function() {
this.posY -= 20;
this.ctx.fillStyle = 'rgba(23,23,23,0.1)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = 'white';
this.ctx.beginPath();
this.ctx.arc(this.posX, this.posY, 10, 0, twoPi, false);
this.ctx.fill();
if (this.posY < 0) {
return false; /* done rendering */
}
else {
return true;
}
};
var canvas = document.getElementById("paper"),
c = canvas.getContext("2d"),
twoPi = Math.PI * 2;
canvas.width = 500;
canvas.height = 700;
c.fillStyle = "#232323";
c.fillRect(0, 0, canvas.width, canvas.height);
c.save();
var renderLine = function() {
console.log('render line');
var posX = Math.random() * canvas.width;
posY = canvas.height;
var animator = new LineAnimator(canvas, posX, posY);
var timer = setInterval(function() {
if(!animator.render()) {
console.log('done rendering');
clearInterval(timer);
setTimeout(renderLine, Math.random() * 2000)
}
}, 30);
};
var ii = 0,
nConcurrentLines = 8;
for (; ii < nConcurrentLines; ++ii ) {
renderLine();
}
};
This definitely looks a bit different than what I think you're going for, but I kind of like the effect. Though, i would take a close look at #MarkE's answer below. I think his approach is much better. There are no timeouts or intervals and it's using the requestAnimationFrame method which seems much cleaner.

Related

Why does my triangle on canvas have artifacts? [duplicate]

I'm doing a Pong game in javascript in order to learn making games, and I want to make it object oriented.
I can't get clearRect to work. All it does is draw a line that grows longer.
Here is the relevant code:
function Ball(){
this.radius = 5;
this.Y = 20;
this.X = 25;
this.draw = function() {
ctx.arc(this.X, this.Y, this.radius, 0, Math.PI*2, true);
ctx.fillStyle = '#00ff00';
ctx.fill();
};
}
var ball = new Ball();
function draw(){
player.draw();
ball.draw();
}
function update(){
ctx.clearRect(0, 0, 800, 400);
draw();
ball.X++;
}
I've tried to put the ctx.clearRect part in the draw() and ball.draw() functions and it doesn't work.
I also tried fillRect with white but it gives the same results.
How can I fix this?
Your real problem is you are not closing your circle's path.
Add ctx.beginPath() before you draw the circle.
jsFiddle.
Also, some tips...
Your assets should not be responsible for drawing themselves (their draw() method). Instead, perhaps define their visual properties (is it a circle? radius?) and let your main render function handle canvas specific drawing (this also has the advantage that you can switch your renderer to regular DOM elements or WebGL further down the track easily).
Instead of setInterval(), use requestAnimationFrame(). Support is not that great at the moment so you may want to shim its functionality with setInterval() or the recursive setTimeout() pattern.
Your clearRect() should be passed the dimensions from the canvas element (or have them defined somewhere). Including them in your rendering functions is akin to magic numbers and could lead to a maintenance issue further down the track.
window.onload = function() {
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
var cvsW = cvs.Width;
var cvsH = cvs.Height;
var snakeW = 10;
var snakeH = 10;
function drawSnake(x, y) {
ctx.fillStyle = '#FFF';
ctx.fillRect(x*snakeW, y * snakeH, snakeW, snakeH);
ctx.fillStyle = '#000';
ctx.strokeRect(x*snakeW, y * snakeH, snakeW, snakeH);
}
// drawSnake(4, 5)
//create our snake object, it will contain 4 cells in default
var len = 4;
var snake = [];
for(var i = len -1; i >=0; i--) {
snake.push(
{
x: i,
y: 0
}
)
};
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH)
for(var i = 0; i < snake.length; i++) {
var x = snake[i].x;
var y = snake[i].y;
drawSnake(x, y)
}
//snake head
var snakeX = snake[0].x;
var snakeY = snake[0].y;
//remove to last entry (the snake Tail);
snake.pop();
// //create a new head, based on the previous head and the direction;
snakeX++;
let newHead = {
x: snakeX,
y: snakeY
}
snake.unshift(newHead)
}
setInterval(draw, 60);
}
my clearRect is not working, what's the problem and solution?

Canvas Javascript Looping

I'm trying to loop my animation, but no matter what I do, it won't loop. I'm pretty new to canvas, javascript and code in general.
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
var background = new Image();
background.src =
"C:/Users/dylan/Desktop/ProjectTwo/Images/fabricationbackground.jpg";
background.onload = function(){
}
//Loading all of my canvas
var posi =[];
posi[1] = 20;
posi[2] = 20;
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi =xy(posi[1],posi[2])
}
function xy(x,y){
ctx.drawImage(background,0,0);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#FFFFFFF";
ctx.fill();
ctx.closePath();
var newpos=[];
newpos[1]= x +dx;
newpos[2]= y +dy;
return newpos;
//Drawing the ball, making it move off canvas.
if (newpos[1] > canvas.width) {
newpos[1] = 20;
}
if (newpos[2] > canvas.height) {
newpos[2] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
}
setInterval(drawballleft, 20);
//Looping the function
Please let me know if I've done something wrong, I really want to learn what I'm doing here. The ball is supposed to go off the canvas, and loop back onto itself, but it goes off the canvas and ends.
Thanks in advance!
I have made a few changes to your code.
First I am using requestAnimationFrame instead of setInterval. http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
Second I am not using an image because I didn't want to run into a CORS issue. But you can put your background image back.
I simplified your posi array to use indexes 0 and 1 instead of 1 and 2 to clean up how you create your array.
I moved your return from before the two ifs to after so the ball will move back to the left or top when it goes off the side. I think that was the real problem you were seeing
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
//Loading all of my canvas
var posi =[20,20];
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi = xy(posi[0],posi[1])
requestAnimationFrame(drawballleft);
}
function xy(x,y){
ctx.fillStyle = '#FFF';
ctx.fillRect(0,0,400,300);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
var newpos=[x+dx,y+dy];
//Drawing the ball, making it move off canvas.
if (newpos[0] > canvas.width) {
newpos[0] = 20;
}
if (newpos[1] > canvas.height) {
newpos[1] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
return newpos;
}
requestAnimationFrame(drawballleft);
canvas {
outline: 1px solid red;
}
<canvas width="400" height="300" id="fabrication"></canvas>
To make it all even simpler...
Use an external script for handling the canvas.
A really good one ;) :
https://github.com/GustavGenberg/handy-front-end#canvasjs
Include it with
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
Then it's this simple:
// Setup canvas
const canvas = new Canvas('my-canvas', 400, 300).start(function (ctx, handyObject, now) {
// init
handyObject.Ball = {};
handyObject.Ball.position = { x: 20, y: 20 };
handyObject.Ball.dx = 10;
handyObject.Ball.dy = 10;
handyObject.Ball.ballRadius = 4;
});
// Update loop, runs before draw loop
canvas.on('update', function (handyObject, delta, now) {
handyObject.Ball.position.x += handyObject.Ball.dx;
handyObject.Ball.position.y += handyObject.Ball.dy;
if(handyObject.Ball.position.x > canvas.width)
handyObject.Ball.position.x = 20;
if(handyObject.Ball.position.y > canvas.height)
handyObject.Ball.position.y = 20;
});
// Draw loop
canvas.on('draw', function (ctx, handyObject, delta, now) {
ctx.clear();
ctx.beginPath();
ctx.arc(handyObject.Ball.position.x, handyObject.Ball.position.y, handyObject.Ball.ballRadius, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
});
I restructured your code and used the external script, and now it looks much cleaner and easier to read and toubleshoot!
JSFiddle: https://jsfiddle.net/n7osvt7y/

How do I add four rotating images to an animated background?

I am trying to add four rotating images to an animated background.
I can only get one image working correctly with my code below.
How can I add in the other three images?
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = document.createElement('img');
img.onload = function(){
render();
}
img.src = 'nano3.png';
function drawImage(img,x,y,r,sx,sy){
sx=sx||0;
sy=sy||0;
r=(r*Math.PI/180)||0;
var cr = Math.cos(r);
var sr = Math.sin(r);
ctx.setTransform(cr,sr,-sr,cr,x-(cr*sx-sr*sy),y-(sr*sx+cr*sy));
ctx.drawImage(img,1,2);
}
var r = 1;
function render(){
requestAnimationFrame(render);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0,800,800);
drawImage(img,50,50,r++,img.width/2,img.height/2);
}
This should help you out, I just created an object known as rotatingimage which stores a location, an image and its current rotation. We call the 'draw' method in a 'setInterval' function call which deals with rotating the canvas and then drawing the sprite correctly.
Just a note rotating many images can cause the canvas to lag also the CurrentRotation variable never gets reset to 0 when it reaches >359 so the CurrentRotation variable will keep going higher and higher, you may want to fix that in the RotatingImage.prototype.Draw function
jsFiddle:https://jsfiddle.net/xd8brfrk/
Javascript
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function RotatingImage(x, y, spriteUrl, rotationSpeed) {
this.XPos = x;
this.YPos = y;
this.Sprite = new Image();
this.Sprite.src = spriteUrl;
this.RotationSpeed = rotationSpeed;
this.CurrentRotation = 0;
}
RotatingImage.prototype.Draw = function(ctx) {
ctx.save();
this.CurrentRotation += 0.1;
ctx.translate(this.XPos + this.Sprite.width/2, this.YPos + this.Sprite.height/2);
ctx.rotate(this.CurrentRotation);
ctx.translate(-this.XPos - this.Sprite.width/2, -this.YPos - this.Sprite.height/2);
ctx.drawImage(this.Sprite, this.XPos, this.YPos);
ctx.restore();
}
var RotatingImages = [];
RotatingImages.push(new RotatingImage(50, 75, "http://static.tumblr.com/105a5af01fc60eb94ead3c9b342ae8dc/rv2cznl/Yd9oe4j3x/tumblr_static_e9ww0ckmmuoso0g4wo4okosgk.png", 1));
RotatingImages.push(new RotatingImage(270, 25, "http://static.tumblr.com/105a5af01fc60eb94ead3c9b342ae8dc/rv2cznl/Yd9oe4j3x/tumblr_static_e9ww0ckmmuoso0g4wo4okosgk.png", 1));
RotatingImages.push(new RotatingImage(190, 180, "http://static.tumblr.com/105a5af01fc60eb94ead3c9b342ae8dc/rv2cznl/Yd9oe4j3x/tumblr_static_e9ww0ckmmuoso0g4wo4okosgk.png", 1));
RotatingImages.push(new RotatingImage(100, 270, "http://static.tumblr.com/105a5af01fc60eb94ead3c9b342ae8dc/rv2cznl/Yd9oe4j3x/tumblr_static_e9ww0ckmmuoso0g4wo4okosgk.png", 1));
setInterval(function() {
ctx.fillStyle = "#000"
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < RotatingImage.length; i++) {
var rotatingImage = RotatingImages[i];
rotatingImage.Draw(ctx);
}
}, (1000 / 60));
you can use save and restore to apply different transform to your drawing
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore

mousemove event not working like expected in Javascript

I have some code below for the start of a snake game that I'm making using HTML5 canvas. For some reason, the red circle that I'm temporarily using to represent my snake is drawing constantly following the path the mouse moves in. and it uses the food as a starting point. Check it out in your browser, because it's really hard to describe. All I want is for the circle to follow the mouse and leave a small trail that ends and doesn't stay on the canvas. How would I go about doing this. Thanks in advance!
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Snake 2.0</title>
</head>
<style>
</style>
<body>
<div>
<canvas id="canvas" width=500 height=500></canvas>
</div>
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
makeFood();
function makeFood() {
foods = [];
for (var i = 0; i < 1; i++){
foods.push(new Food());
}
}
function Food() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 10;
}
function drawFood() {
for (var i = 0; i < 1; i++){
foods.push(new Food());
}
for (var i = 0; i < foods.length; i++){
var f = foods[i];
context.beginPath();
var grd = context.createRadialGradient(f.x, f.y, (f.radius - (f.radius - 1)), f.x + 1, f.y + 1, (f.radius));
grd.addColorStop(0, 'red');
grd.addColorStop(1, 'blue');
context.arc(f.x, f.y, f.radius, 0, 2 * Math.PI, true);
context.fillStyle = grd;
context.fill();
}
}
function makePower() {
powers = [];
for (var i = 0; i < 1; i++){
powers.push(new Power());
}
}
function Power() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 8;
}
function drawPower() {
for (var i = 0; i < powers.length; i++){
var p = powers[i];
context.beginPath();
var grd = context.createRadialGradient(p.x, p.y, (p.radius - (p.radius - 1)), p.x + 1, p.y + 1, (p.radius));
grd.addColorStop(0, 'green');
grd.addColorStop(1, 'yellow');
context.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
context.fillStyle = grd;
context.fill();
}
}
canvas.addEventListener("mousemove", function(event) {
move(event);
});
function move(e) {
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var a = e.clientX;
var b = e.clientY;
context.arc(a, b, 20, 0, 2 * Math.PI, true);
context.fillStyle = "red";
context.fill();
}
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var functions = [drawFood];
var timer = setInterval(function(){
drawFood();
}, 5000);
function stop() {
clearInterval(timer);
}
canvas.addEventListener("click", stop);
//timer = setInterval(start, 1000);
//timer = setInterval(start, 5000);
</script>
</body>
</html>
You could start by adding "context.beginPath();" in your "move" function, before "context.arc(a, b, 20, 0, 2 * Math.PI, true);", line 102-103 in my editor.
function move(e) {
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
var a = e.clientX;
var b = e.clientY;
context.beginPath();
context.arc(a, b, 20, 0, 2 * Math.PI, true);
context.fillStyle = "red";
context.fill();
}
Here is the fiddle : http://jsfiddle.net/sd5hh57b/1/
You should store the positions you move along in an array. Then a new timer should revisit those discs and redraw them in a more faded color each time it ticks, until a disc becomes black. Then it should be removed from that array.
Here is fiddle that does that.
The change in the code starts at canvas.addEventListener("mousemove",... and goes like this:
canvas.addEventListener("mousemove", function(event) {
// Replaced move function by drawDisc function,
// which needs coordinates and color intensity
drawDisc(event.clientX, event.clientY, 0xF);
});
// Array to keep track of previous positions, i.e. the trail
var trail = [];
function drawDisc(x, y, red) {
context.beginPath();
context.arc(x, y, 20, 0, 2 * Math.PI, true);
context.fillStyle = '#' + red.toString(16) + '00000';
context.fill();
// If disc is not completely faded out, push it in the trail list
if (red) {
trail.push({x: x, y: y, red: red});
}
}
// New function to regularly redraw the trail
function fadeTrail() {
var discs = trail.length;
// If there is only one disc in the trail, leave it as-is,
// it represents the current position.
if (discs > 1) {
for (var i = discs; i; i--) {
// take "oldest" disc out of the array:
disc = trail.shift();
// and draw it with a more faded color, unless it is
// the current disc, which keeps its color
drawDisc(disc.x, disc.y, disc.red - (i === 1 ? 0 : 1));
}
}
}
// New timer to fade the trail
var timerFade = setInterval(function(){
fadeTrail();
}, 10);
I think the comments will make clear what this does. Note that the colors of the discs go from 0xF00000 to 0xE00000, 0xD00000, ... , 0x000000. Except the current disc, that one keeps its 0xF00000 color all the time.
The other answers are right :
Use beginPath() at each new arc() to create a new Path and avoid context.fill() considers the whole as a single Path.
Use a trail Array to store your last positions to draw the trail.
But, the use of setTimeout and setInterval should be avoided (and even further the use of multiple ones).
Modern browsers do support requestAnimationFrame timing method, and for olders (basically IE9), you can find polyfills quite easily. It has a lot of advantages that I won't enumerate here, read the docs.
Here is a modified version of your code, which uses a requestAnimationFrame loop.
I also created two offscreen canvases to update your foods and powers, this way they won't disappear at each draw. Both will be painted in the draw function.
I changed the mousemove handler so it only updates the trail array, leaving the drawing part in the draw loop. At each call, it will set a moving flag that will let our draw function know that we are moving the mouse. Otherwise, it will start to remove old trail arcs from the Array.
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
// create other contexts (layer like) for your food and powers
var foodContext = canvas.cloneNode(true).getContext('2d');
var pwrContext = canvas.cloneNode(true).getContext('2d');
// a global to tell weither we are moving or not
var moving;
// a global to store our animation requests and to allow us to pause it
var raf;
// an array to store our trail position
var trail = [];
// here we can determine how much of the last position we'll keep at max (can then be updated if we ate some food)
var trailLength = 10;
// your array for the foods
var foods = [];
// a global to store the last time we drawn the food, no more setInterval
var lastDrawnFood = 0;
// start the game
draw();
function makeFood() {
foods.push(new Food());
}
function Food() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 10;
}
function drawFood() {
// clear the food Canvas (this could be done only if we ate some, avoiding the loop through all our foods at each call of this method)
foodContext.clearRect(0, 0, canvas.width, canvas.height);
foods.push(new Food());
for (var i = 0; i < foods.length; i++) {
var f = foods[i];
// draw on the food context
foodContext.beginPath();
foodContext.arc(f.x, f.y, f.radius, 0, 2 * Math.PI, true);
var foodGrd = foodContext.createRadialGradient(f.x, f.y, (f.radius - (f.radius - 1)), f.x + 1, f.y + 1, (f.radius));
foodGrd.addColorStop(0, 'red');
foodGrd.addColorStop(1, 'blue');
foodContext.fillStyle = foodGrd;
foodContext.fill();
}
}
// I'll let you update this one
function makePower() {
powers = [];
for (var i = 0; i < 1; i++) {
powers.push(new Power());
}
}
function Power() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.radius = 8;
}
function drawPower() {
pwrContext.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < powers.length; i++) {
var p = powers[i];
var pwrGrd = pwrContext.createRadialGradient(p.x, p.y, (p.radius - (p.radius - 1)), p.x + 1, p.y + 1, (p.radius));
pwrGrd.addColorStop(0, 'green');
pwrGrd.addColorStop(1, 'yellow');
pwrContext.beginPath();
pwrContext.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
pwrContext.fillStyle = pwrGrd;
pwrContext.fill();
}
}
// the event object is already passed, no need for an anonymous function here
canvas.addEventListener("mousemove", move);
function move(e) {
// we paused the game, don't update our position
if (!raf) return;
// update the snake
var a = e.clientX - canvas.offsetLeft;
var b = e.clientY - canvas.offsetTop;
trail.splice(0, 0, {
x: a,
y: b
});
// tell our draw function that we moved
moving = true;
}
function draw(time) {
// our food timer
if (time - lastDrawnFood > 5000) {
lastDrawnFood = time;
drawFood();
}
// clear the canvas
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
// draw the food
context.drawImage(foodContext.canvas, 0, 0);
// draw the power
context.drawImage(pwrContext.canvas, 0, 0);
//draw the snake
for (var i = 0; i < trail.length; i++) {
// decrease the opacity
opacity = 1 - (i / trail.length);
context.fillStyle = "rgba(255, 0,0," + opacity + ")";
// don't forget to create a new Path for each circle
context.beginPath();
context.arc(trail[i].x, trail[i].y, 20, 0, 2 * Math.PI, true);
context.fill();
}
// if we're not moving or if our trail is too long
if ((!moving || trail.length > trailLength) && trail.length > 1)
// remove the oldest trail circle
trail.pop();
// we're not moving anymore
moving = false;
// update the animation request
raf = requestAnimationFrame(draw);
}
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
function toggleStop() {
if (!raf) {
// restart the animation
raf = window.requestAnimationFrame(draw);
} else {
// cancel the next call
cancelAnimationFrame(raf);
raf = 0;
}
}
canvas.addEventListener("click", toggleStop);
html, body{margin:0;}
<canvas id="canvas" width=500 height=500></canvas>

HTML Canvas create multiple rectangles that are animated

I am trying to create and HTML5 game using Canvas that needs to have multiple blocks being created (create a new block every second). Each time a new block is created, it needs to "fall" down the screen using an animation. I can get the animation to work correctly for one element, but I'm not sure how to do it with creating multiple (and creating them at a time interval different that the interval being used for the animation FPS).
//update and animate the screen
var FPS = 60;
setInterval(function() {
//update();
draw();
}, 1000/FPS);
var y = 30;
var dy = 5;
//5 + Math.floor(Math.random()*6)
//draw the screen
function draw() {
//var y = 30;
context.save();
context.clearRect(0,0,canvas.width, canvas.height);
rounded_rect(1*40, y, 40, 40, 5, "red", "black");
if (this.y < 360){
y += dy;
}
context.restore();
};
rounded_rect is just another function that creates a rounded rectangle. It works correctly.
Live Demo
This is one way to do it. Create an array to hold your blocks, and a Block object to use for the boxes. I then create two methods, update and render which update the box and draw it to the canvas.
Block Object
function Block(x,y,width,height){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
Block.prototype.update = function(){
if(this.y < 360){
this.y+=dy
}else{
this.y = 0;
}
};
Block.prototype.render = function(){
context.fillRect(this.x, this.y, this.width, this.height);
};
Checking if time passed has met the threshold
As for creating new ones independent of the frame rate you can just do a time check to see if 1 second has passed since the last time you created a block.
if(+new Date() > lastTime + minWait){
lastTime = +new Date();
// add a new block
blocks.push(new Block(Math.random()*300, 0,20,20));
}
Basically how this works is if the current time is greater than the last time + 1 second it will create a new one, and reset the lastTime to the current time.
Additional Info
I highly suggest you look at requestAnimationFrame it is the proper and defacto way to do any sort of canvas rendering.
Full source
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d");
//update and animate the screen
var FPS = 60;
setInterval(function() {
//update();
draw();
}, 1000/FPS);
var dy = 5,
blocks = [],
minWait = 1000,
lastTime = +new Date();
function Block(x,y,width,height){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
Block.prototype.update = function(){
if(this.y < 360){
this.y+=dy
}else{
this.y = 0;
}
};
Block.prototype.render = function(){
context.fillRect(this.x, this.y, this.width, this.height);
};
//draw the screen
function draw() {
if(+new Date() > lastTime + minWait){
lastTime = +new Date();
blocks.push(new Block(Math.random()*300, 0,20,20));
}
context.clearRect(0,0,canvas.width, canvas.height);
blocks.forEach(function(e){
e.update();
e.render();
});
};

Categories

Resources