basically what I'm trying to do is create a 'raining effect' on the canvas (doesn't exactly look like rain at the moment but I will sort that later)
This is my JavaScript so far:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas");
cx = canvas.getContext("2d");
function rectangle (x, y, w, h) {
var randomx = Math.floor(Math.random() * canvas.width - 50);
var randomy = Math.floor(Math.random() * canvas.height - 100);
this.x = randomx || x || 0;
this.y = randomy || y || 0;
this.w = w || 0;
this.h = h || 0;
this.draw = function () {
cx.fillStyle = "blue";
cx.fillRect(this.x, this.y, this.w, this.h);
};
}
var myRectangle = new rectangle(window.randomx, window.randomy, 10, 10);
function loop () {
cx.clearRect(0, 0, canvas.width, canvas.height);
myRectangle.y++;
myRectangle.draw();
requestAnimFrame(loop);
}
loop();
Basically, it will create a 10 by 10 blue block at a random y and x point of the canvas, what I need to do is keep adding this blue block over and over onto the canvas. I tried including this for loop into the 'loop' function:
for (var i = 0; i < 20; i++) {
var myRectangle = new rectangle(window.randomx, window.randomy, 10, 10);
}
But this just keeps flashing the block at random points (I understand why, it is because it keeps overwriting the variable and placing it at a new point), would anyone be able to help me? I know it would be easier to use jQuery for this, but I'm using JavaScript only
Here is a fiddle for what it looks like at the moment (without the for loop) thanks in advance!
jsfiddle
Make an array of rectangles instead:
myRectangle[i] = new rectangle(...);
This way the previously generated ones won't get overwritten/destroyed.
Here's an example using an array. You also want to make sure you remove them when they're off the screen (note expired variable)
http://jsfiddle.net/8Jqzx/2/
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas");
cx = canvas.getContext("2d");
function rectangle (x, y, w, h) {
var randomx = Math.floor(Math.random() * canvas.width - 50);
var randomy = Math.floor(Math.random() * canvas.height - 100);
this.x = randomx || x || 0;
this.y = randomy || y || 0;
this.w = w || 0;
this.h = h || 0;
this.expired = false;
this.draw = function () {
cx.fillStyle = "blue";
cx.fillRect(this.x, this.y, this.w, this.h);
};
this.update = function () {
this.y++;
if (y > canvas.height) {
this.expired = true;
}
}
}
var rectangles = new Array();
function newRect () {
rectangles.push(new rectangle(window.randomx, window.randomy, 10, 10));
}
var timing = 0;
function loop () {
cx.clearRect(0, 0, canvas.width, canvas.height);
if (timing%10 == 0) {
newRect();
}
for (var i = 0; i < rectangles.length; i++) {
rectangles[i].update();
rectangles[i].draw();
if (rectangles[i].expired) {
rectangles.splice(i, 1);
i--;
}
}
timing++;
requestAnimFrame(loop);
}
loop();
But in this one you have them appear at the top (more rain-like): http://jsfiddle.net/8Jqzx/3/
Related
I am currently trying to learn JavaScript and am experimenting with canvas animation. I tried to create firework effect using canvas. I am using this source as a reference.
So far, I have successfully created a single particle and let it shoot to the sky. However, I couldn't let it explode...
Is there something wrong with my code and logic? I would be really appreciate if someone could kindly look into my code and give me some advices what should I do to correct it.
Following is my .js code:
const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');
let w = canvas.width;
let h = canvas.height;
w = window.innerWidth;
h = window.innerHeight;
let particles = [];
class Particle{
constructor(pos, vel, target){
// firework animation properties
this.gravity = 1;
this.alpha = 1;
this.easing = Math.random() * 0.02;
this.fade = Math.random() * 0.1;
this.pos = {
x: pos.x || 0,
y: pos.y || 0
};
this.vel = {
x: vel.x || 0,
y: vel.y || 0
};
this.target = {
y: target.y || 0
};
this.lastPos = {
x: this.pos.x,
y: this.pos.y
};
}
update(){
this.lastPos.x = this.pos.x;
this.lastPos.y = this.pos.y;
this.vel.y += this.gravity;
this.pos.y += this.vel.y;
this.alpha -= this.fade;
this.distance = (this.target.y - this.pos.y);
// ease the position
this.pos.y += this.distance * (0.03 + this.easing);
// cap to 1
this.alpha = Math.min(this.distance * this.distance * 0.00005, 1);
this.pos.x += this.vel.x;
return (this.alpha < 0.005);
}
draw(){
let x = Math.round(this.pos.x),
y = Math.round(this.pos.y),
xVel = (x - this.lastPos.x) * -5,
yVel = (y - this.lastPos.y) * -5;
context.fillStyle = "rgba(255,255,255,0.3)";
context.beginPath();
context.moveTo(this.pos.x, this.pos.y);
context.lineTo(this.pos.x + 1.5, this.pos.y);
context.lineTo(this.pos.x + xVel, this.pos.y + yVel);
context.lineTo(this.pos.x - 1.5, this.pos.y);
context.closePath();
context.fill();
}
}
const init = () =>{
// set canvas size to window size
onResize();
document.addEventListener('mouseup', createParticle, true);
// set off fireworks
update();
}
const update = () =>{
// clear up the canvas
clearCanvas();
// start the animation loop
requestAnimFrame(update);
createFireworks();
}
const createFireworks = () =>{
for(let i = 0; i < particles.length; i++){
if(particles[i].update()){
// kill off the firework, replace it
// with the particles for the exploded version
particles.splice(particles.length, 1);
explodeFireworks(particles[i]);
}
particles[i].draw();
}
console.log(particles.length);
}
const explodeFireworks = (firework) =>{
let count = 100;
let angle = (Math.PI * 2) / count;
for(let i = 0; i < count; i++){
let randomVelocity = 4 + Math.random() * 4;
let particleAngle = count * angle;
createParticle(
firework.pos,
{
x: Math.cos(particleAngle) * randomVelocity,
y: Math.sin(particleAngle) * randomVelocity
},
null
);
}
}
//without parameters create particle, with parameters create firework
const createParticle = (pos, vel, target) =>{
pos = pos || {};
vel = vel || {};
target = target || {};
particles.push(
new Particle(
{
x: w/2,
y: h
},
{
x: vel.x || Math.random() * 3 - 1.5,
y: vel.y || 0
},
{
y: target.y || h/2
}
)
);
}
const clearCanvas = () =>{
context.clearRect(0,0,canvas.width, canvas.height);
}
const onResize =() => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
// Simple polyfill for browsers that don't support requestAnimationFrame
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
window.addEventListener('load', init, false);
I am using HTML canvas to draw multiple squares. I have 2 functions: 1) draw a square and 2) draw multiple squares inside a loop.
Now I want to animate these squares using requestAnimationFrame to draw these square one at a time. How can I achieve this. Here is a jsFiddle
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
function rect(x, y, w, h) {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(number, size) {
for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
}
}
drawRect(10, 5);
I provided a frame limiter and tween to show you different ways of animating. The frame limiter has the steps in your example and the tween has as many steps as it takes to complete in a given amount of time.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
//requestAnim shim layer by Paul Irish
//http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
function rect(x, y, w, h, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(i, size, color) {
//for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2, color);
//}
}
var i = 0;
var incr = 1;
var i_max = 10;
var size = 5;
var fps = 10;
var delay = 1000 / fps;
var lastFrame = 0;
var animationTime = 5000
var tweenStep = i_max / ((animationTime/1000) * 60);
var j = 0;
function animateRect() {
// draw at 60fps
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawRect(i, size, "#0000FF");
// This is a frame limiter.
var currentFrame = Date.now();
if ((currentFrame - lastFrame) >= delay) {
i += incr;
if (i >= i_max) i = i_max - 2, incr = -1;
if (i < 0) i = 1, incr = 1;
lastFrame = currentFrame;
}
// this is a tween. The step is calculated for the desired time.
drawRect(j, size, "#FF0000");
j += tweenStep;
if (j >= i_max) tweenStep *= -1,j=i_max-1;
if (j < 0) tweenStep *= -1, j=0;
requestAnimFrame(animateRect);
//draw rectangle one by one here...
}
animateRect();
//drawRect(10, 5);
<canvas id="canvas" width="600" height="600"></canvas>
You can do something like
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before = new Date().getTime(), // last draw time in ms
now; // current time in ms
function animateRect() {
// get the current time to find if we should draw
now = new Date().getTime();
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
Edit:
As Blindman67 pointed out below, requestAnimFrame passes the current timestamp since the beginning of the animation to the callback. Here's how to take advantage of it:
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before; // last draw time in ms
function animateRect(now) {
if ( !before ) before = now;
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
However, this would necessitate modifying the shim the OP is using, in order to pass the current timestamp to the callback in setTimeout:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60, new Date.now());
};
})();
I am using HTML canvas to draw multiple squares. I have 2 functions: 1) draw a square and 2) draw multiple squares inside a loop.
Now I want to animate these squares using requestAnimationFrame to draw these square one at a time. How can I achieve this. Here is a jsFiddle
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
function rect(x, y, w, h) {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(number, size) {
for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
}
}
drawRect(10, 5);
I provided a frame limiter and tween to show you different ways of animating. The frame limiter has the steps in your example and the tween has as many steps as it takes to complete in a given amount of time.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
//requestAnim shim layer by Paul Irish
//http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
function rect(x, y, w, h, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(i, size, color) {
//for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2, color);
//}
}
var i = 0;
var incr = 1;
var i_max = 10;
var size = 5;
var fps = 10;
var delay = 1000 / fps;
var lastFrame = 0;
var animationTime = 5000
var tweenStep = i_max / ((animationTime/1000) * 60);
var j = 0;
function animateRect() {
// draw at 60fps
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawRect(i, size, "#0000FF");
// This is a frame limiter.
var currentFrame = Date.now();
if ((currentFrame - lastFrame) >= delay) {
i += incr;
if (i >= i_max) i = i_max - 2, incr = -1;
if (i < 0) i = 1, incr = 1;
lastFrame = currentFrame;
}
// this is a tween. The step is calculated for the desired time.
drawRect(j, size, "#FF0000");
j += tweenStep;
if (j >= i_max) tweenStep *= -1,j=i_max-1;
if (j < 0) tweenStep *= -1, j=0;
requestAnimFrame(animateRect);
//draw rectangle one by one here...
}
animateRect();
//drawRect(10, 5);
<canvas id="canvas" width="600" height="600"></canvas>
You can do something like
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before = new Date().getTime(), // last draw time in ms
now; // current time in ms
function animateRect() {
// get the current time to find if we should draw
now = new Date().getTime();
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
Edit:
As Blindman67 pointed out below, requestAnimFrame passes the current timestamp since the beginning of the animation to the callback. Here's how to take advantage of it:
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before; // last draw time in ms
function animateRect(now) {
if ( !before ) before = now;
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
However, this would necessitate modifying the shim the OP is using, in order to pass the current timestamp to the callback in setTimeout:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60, new Date.now());
};
})();
I'm trying to animate a sine wave in JS but it's not acting as expected. I'm using a <canvas> element along with window.requestAnimationFrame() method but it's a CPU hog and as i change frequency with the slider it just break and show random waveforms. I also don't know if drawing adjacent lines is the best way to represent a sine wave. Please note that i'll use vanilla JS and that the sine's frequency and amplitude are variables set by sliders. Thanks in advance.
This is what i got so far: http://cssdeck.com/labs/8cq5vclp
UPDATE: i worked on it and this is the new version: http://cssdeck.com/labs/sbfynjkr
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
cHeight = canvas.height,
cWidth = canvas.width,
frequency = document.querySelector("#f").value,
amplitude = 80,
x = 0,
y = cHeight / 2,
point_y = 0;
window.onload = init;
function init() {
document.querySelector("#f").addEventListener("input", function() {
frequency = this.value;
document.querySelector("#output_f").value = frequency;
}, false);
drawSine();
}
function drawSine() {
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.beginPath();
ctx.moveTo(0, y);
ctx.strokeStyle = "red";
ctx.lineTo(cWidth, y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "black";
for (x = 0; x < 600; x++) {
point_y = amplitude * -Math.sin((frequency / 95.33) * x) + y;
ctx.lineTo(x, point_y);
}
ctx.stroke();
ctx.closePath();
requestAnimationFrame(drawSine);
}
canvas {
border: 1px solid red;
margin: 10px;
}
<input id="f" type="range" min="0" max="20000" value="20" step="1">
<output for="f" id="output_f">20</output>
<canvas width="600px" height="200px"></canvas>
I've messed around with sine waves quite a bit, because I'm working on a little project that involves animated sine waves. I've got some code you might be interested in taking a look at. Like mentioned earlier, you need to make sure you are using the right increment in your loop so the lines do not look jagged.
https://jsfiddle.net/uawLvymc/
window.requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(f) {
return setTimeout(f, 1000 / 60)
};
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var startTime = new Date().getTime();
function getPath(height) {
var width = canvas.width;
var spacing = 0.08;
var loopNum = 0;
var pointList = [];
var i = 0;
for (i = 0; i < width / 2; i++) {
pointList[loopNum] = [loopNum, Math.sin(loopNum * spacing) * (i * height) + 100];
loopNum++;
}
for (i = width / 2; i > 0; i--) {
pointList[loopNum] = [loopNum, Math.sin(loopNum * spacing) * (i * height) + 100];
loopNum++;
}
return pointList;
}
function draw() {
var currentTime = new Date().getTime();
var runTime = currentTime - startTime;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "rgb(80, 100, 230)";
var height = Math.sin(runTime * 0.008) * 0.2;
var pointList = getPath(height);
for (var i = 0; i < 500; i++) {
if (i === 0) {
ctx.moveTo(pointList[0][0], pointList[0][1]);
} else {
ctx.lineTo(pointList[i][0], pointList[i][1]);
}
}
ctx.stroke();
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
Sorry I didn't really edit down the code, it's just a direct copy from what I was working on. Hope it helps though.
See if this example could help you a little
Sine Wave Example canvas
function init()
{
setInterval(OnDraw, 200);
}
var time = 0;
var color = "#ff0000";
function OnDraw()
{
time = time + 0.2;
var canvas = document.getElementById("mycanvas");
var dataLine = canvas.getContext("2d");
var value = document.getElementById("lineWidth");
dataLine.clearRect(0, 0, canvas.width, canvas.height);
dataLine.beginPath();
for(cnt = -1; cnt <= canvas.width; cnt++)
{
dataLine.lineTo(cnt, canvas.height * 0.5 - (Math.random() * 2 + Math.cos(time + cnt * 0.05) * 20 ));
}
dataLine.lineWidth = value.value * 0.1;
dataLine.strokeStyle = color;
dataLine.stroke();
}
I am doing some work with HTML canvases. Unfortunately I have ran into a very weird problem. At some point during development of the code the web page started to hang the browser. There were no tight loops apart from the requestAnimFrame shim so I took it back to basics and have found a very odd thing.
The code below will animate a circle across the screen. This works perfectly fine. If I comment out the code to draw the circle (marked in the code) it then grinds the browser to a halt. What is happening?
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animate(lastTime, myCircle) {
//return;
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// update
var date = new Date();
var time = date.getTime();
var timeDiff = time - lastTime;
var linearSpeed = 100;
// pixels / second
var linearDistEachFrame = linearSpeed * timeDiff / 1000;
var currentX = myCircle.x;
if(currentX < canvas.width - myCircle.width - myCircle.borderWidth / 2) {
var newX = currentX + linearDistEachFrame;
myCircle.x = newX;
}
lastTime = time;
// clear
drawGrid();
//draw a circle
context.beginPath();
context.fillStyle = "#8ED6FF";
context.arc(myCircle.x, myCircle.y, myCircle.width, 0, Math.PI*2, true);
context.closePath();
context.fill();
context.lineWidth = myCircle.borderWidth;
context.strokeStyle = "black";
context.stroke();
// request new frame
requestAnimFrame(function() {
animate(lastTime, myCircle);
});
}
$(document).ready(function() {
var myCircle = {
x: 50,
y: 50,
width: 30,
height: 50,
borderWidth: 2
};
//drawGrid();
var date = new Date();
var time = date.getTime();
animate(time, myCircle);
});
function drawGrid(){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
context.lineWidth = 1;
for (var x = 0; x <= canvas.width; x += 40) {
context.moveTo(0 + x, 0);
context.lineTo(0 + x, canvas.height);
}
for (var x = 0; x <= canvas.height; x += 40) {
context.moveTo(0, 0 + x);
context.lineTo(canvas.width, 0 + x);
}
context.strokeStyle = "#ddd";
context.stroke();
}
And my canvas is declared like so:
<canvas id="myCanvas" width="700" height="400"></canvas>
Turns out that the reason it worked when I draw the circle was because that code contained a closePath function. Adding that to the drawGrid function below fixes the issue.
function drawGrid(){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
context.lineWidth = 1;
context.beginPath();
for (var x = 0; x <= canvas.width; x += 40) {
context.moveTo(0 + x, 0);
context.lineTo(0 + x, canvas.height);
}
for (var x = 0; x <= canvas.height; x += 40) {
context.moveTo(0, 0 + x);
context.lineTo(canvas.width, 0 + x);
}
context.closePath();
context.strokeStyle = "#ddd";
context.stroke();
}