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();
});
};
Related
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?
I want to animate a rect on a canvas. It technically works, but the canvas is not clearing before each frame and it leaves a mark on the ground (sort of like a snail). I have done research and everything seems to point to the use of ctx.beginPath() as the solution to my problem, but when I try to use it here, it doesn't work. What am I doing wrong?
Here is the raw javascript:
// create a canvas element
var canvas = document.createElement("canvas");
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas);
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d");
var bug = new Bug(0,0);
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// set the 0,0 point to the middle of the canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
}
function draw() { //Do the drawing
ctx.beginPath();
bug.update();
bug.draw();
window.requestAnimationFrame(function(){draw()});
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup();
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup);
// sin(pi) == 0
// cos(pi) == -1
// sin(2pi) == 0
// cos(2pi) == 1
// degrees to radians: {{ deg * (pi/180) = rad }}
function randomRange(max, min) {
return Math.floor(Math.random() * (max - min) + min);
}
function Bug(x, y) {
this.x = x;
this.y = y;
this.jitter = 10;
this.speed = 1;
this.deg = 0;
this.rad = 0;
this.update = function() {
//update degrees
this.deg += randomRange(this.jitter, -this.jitter);
//convert degrees into radians
this.rad = this.deg * (Math.PI/180);
//update coordinates
this.x += this.speed * (Math.cos(this.rad));
this.y += this.speed * (Math.sin(this.rad));
};
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 50, 50);
ctx.fill();
};
}
The beginPath function doesn't clear the screen it starts a path to draw, to clear the screen you should use clearRect instead and in your specific situation you should use:
ctx.clearRect(-canvas.width, -canvas.height, canvas.width*2, canvas.height*2);
I am new to Canvas and want to loop background Image in the below smoke effect. On searching, I have found an example that how we can loop background Image in canvas Link to looping animation so I tried integrating the looping code with the smoke effect but no success. Any help will be appreciated.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
context.drawImage(backImg, 0, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-lights';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas" ></canvas>
I just added a few lines. Hopefully you can spot them. I commented everything I added.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
// x position of scrolling image
var imageX = 0;
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
// draw twice to cover wrap around
context.drawImage(backImg, imageX, 0, canvasWidth, canvasHeight);
context.drawImage(backImg, imageX + canvasWidth, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-light';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
// incrementally change image position of background to scroll left
imageX -= maxVelocity;
if (imageX < -canvasWidth) {
imageX += canvasWidth;
}
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas"></canvas>
Just to add to the answer given some additional improvements to the code structure.
Use requestAnimationFrame to call render calls.
Don't expose properties of objects if not needed.
Don't use forEach iteration in time critical code. Use for loops.
Use constants where ever possible.
Comments that state the obvious are just noise in the source code making it harder to read. Limit comment to abstracts that may not be obvious to another programmer reading the code.
eg
// If an image is set draw it
if (this.image) {
Really is that comment of any use to anyone. Comments should help not degrade the readability of code.
Also the original code tried to set the global composite operations to soft-lights this is not a know operation. I corrected it to soft-light which can on some machines, be a very slow render operation. It may pay to selected another operation for machines that are slow. This can be done by simply monitoring the render time of particles and switching operation type is too slow.
A quick rewrite of the OP's code.
const particles = [];
const particleCount = 60;
const maxVelocity = 2;
var canvasWidth = innerWidth;
var canvasHeight = innerHeight;
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
var ctx;
const backgroundColor = "rgba(255,255,255, .5)";
const backgroundSpeed = -0.1;
var looping = false;
var totalSeconds = 0;
var lastTime = 0;
var frameTime = (1000 / 30) - (1000 / 120); // one quater frame short to
// allow for timing error
var imageCount = 0;
const backImg = new Image();
const imageObj = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
backImg.onload = imageObj.onload = imageLoad;
function imageLoad(){
imageCount += 1;
if(imageCount === 2){
init();
}
}
function init() {
var canvas = myCanvas;
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext('2d');
for (var i = 0; i < particleCount; i += 1) {
particles.push(new Particle(ctx));
}
lastTime = performance.now();
requestAnimationFrame(mainLoop);
}
function mainLoop(time){
if(time-lastTime > frameTime){
lastTime = time;
update();
draw(time);
}
requestAnimationFrame(mainLoop);
}
const rand = (min, max) => Math.random() * (max - min) + min; // names are best short (short only without ambiguity)
function Particle(ctx) {
var x, y, xVel, yVel, radius, image;
const color = "rgba(0, 255, 255, 1)";
x = rand(0, canvasWidth),
y = rand(borderTop, borderBottom);
xVel = rand(-maxVelocity, maxVelocity);
yVel = rand(-maxVelocity, maxVelocity);
radius = 5;
image = imageObj;
this.draw = function () { ctx.drawImage(image, x - 128, y - 128) }
this.update = function () {
x += xVel;
y += yVel;
if (x >= canvasWidth) {
xVel = -xVel;
x = canvasWidth;
}
else if (x <= 0) {
xVel = -xVel;
x = 0;
}
if (y >= borderBottom) {
yVel = -yVel;
y = borderBottom;
}
else if (y <= borderTop) {
yVel = -yVel;
y = borderTop;
}
}
}
function draw(time) {
var i,x;
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
x = time * backgroundSpeed;
x = ((x % canvasWidth) + canvasWidth) % canvasWidth;
ctx.drawImage(backImg, x, 0, canvasWidth, canvasHeight);
ctx.drawImage(backImg, x - canvasWidth, 0, canvasWidth, canvasHeight);
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.globalAlpha = 0.75;
ctx.globalCompositeOperation = 'soft-light';
for(i = 0; i < particles.length; i += 1){
particles[i].draw();
}
}
function update() {
for(i = 0; i < particles.length; i += 1){
particles[i].update();
}
}
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=myCanvas></canvas>
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.
I'm using this code: http://jsfiddle.net/jonnyc/Ujz4P/5/
Here is a customized fiddle http://jsfiddle.net/6fwFY/4/ where I'm using the canvas positioned absolutely on the text with only 3 particles, and it shows the problem: moving images leaving traces behind them.
Is there a way to fix this so moving images wouldn't let those traces so it would look better?
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 30;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = 400;
var canvasHeight = 400;
// Create an image object (only need one instance)
var imageObj = new Image();
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "http://www.blog.jonnycornwell.com/wp-content/uploads/2012/07/Smoke10.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if(this.image){
this.context.drawImage(this.image, this.x-128, this.y-128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= canvasHeight) {
this.yVelocity = -this.yVelocity;
this.y = canvasHeight;
}
// Check if has crossed the top edge
else if (this.y <= 0) {
this.yVelocity = -this.yVelocity;
this.y = 0;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image){
this.image = image;
}
}
// A function to generate a random number between 2 values
function generateRandom(min, max){
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for(var i=0; i < particleCount; ++i){
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(0, canvasHeight));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
}
}
else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// Clear the drawing surface and fill it with a black background
context.fillStyle = "rgba(0, 0, 0, 0.5)";
context.fillRect(0, 0, 400, 400);
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
You should clear canvas between each new draw, see if that fits your needs:
DEMO jsFiddle
context.clearRect(0, 0, canvasWidth, canvasHeight);