Hello and thank you for your help in advance.
I am trying to push/create a new "ring" every couple seconds. I have a ring with a couple variables for the X and Y. The problem I am encountering is, how do I get a new ring and also increment the variables? I need a new variable name for every ring?
Here is how far I have gotten so far:
http://codepen.io/hossman/pen/AfwkF
You can see in the demo how 1 ring goes out, but I want more than 1 ring to go out of my eyes. So for instance 1 ring goes and then it waits a second and then shoots out another ring, so now there are 2 rings on the canvas, then 3, then 4, etc.... I have thought of multiple ways like using arrays and setTimeouts, but I cant put my finger on it. The only other idea I have is to create multiple variables with different names and have each ring be incremented, but thats not very D.R.Y.
Anyhelp?
Please ask questions if I didn't explain it good enough. Thanks again!
Add this to your global vars at the top (and set to whatever you want the distance to be between circles):
var distanceApart = 40;
Then update your main loop like this:
requestAnimationFrame(function print() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var leftRing = new Ring(x, y);
var rightRing = new Ring(x2, y2);
var temp = startRadius;
var temp2 = 0;
while(temp > 0){
leftRing.draw(ctx, startRadius - temp2 , 'red');
rightRing.draw(ctx, startRadius - temp2 , 'red');
temp2 = temp2 + distanceApart;
temp = temp - distanceApart;
}
startRadius += increase;
requestAnimationFrame(print);
});
Forked here: http://codepen.io/anon/pen/plBmj
(Looks very memorizing!)
I would rewrite parts of your code to enable this. For example I would rewrite your Ring class as follows:
var Ring = defclass({
constructor: function (x, y, r) {
this.x = x;
this.y = y;
this.r = r;
},
draw: function (context) {
context.beginPath();
context.arc(this.x, this.y, this.r, 0, Math.PI * 2);
context.stroke();
return this;
},
addRadius: function (r) {
return new Ring(this.x, this.y, this.r + r);
}
});
Your Ring class constructor now takes x, y and a radius r. The addRadius function returns a new Ring instead of mutating the original one. This is good because immutability makes your code easier to work with. Oh, and defclass is declared as:
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Then we create the two rings for your eyes:
var radius = 10;
var delta = 0.1;
var left = new Ring(cx - (cx / 3.6), cy - 5, radius);
var right = new Ring(cx + (cx / 3.6), cy - 10, radius);
After that we call the animation loop:
var interval = 50 / 3;
var start = Date.now();
loop(start, [left, right]);
Since we want to playback at 60 FPS the interval is 1000 / 60 which can be simplified to 50 / 3. The animation loop is defined as follows:
function loop(last, rings) {
var next = last + interval;
context.clearRect(0, 0, width, height);
var newRings = rings.map(function (ring) {
return ring.draw(context).addRadius(delta);
});
var now = Date.now();
setTimeout(loop, next - now, next,
Math.floor((now - start) / 1000) === rings.length / 2 ?
[left, right].concat(newRings) : newRings);
}
Here's what's happening:
First we clear the screen.
Then we draw all the rings and increase their size.
If one second has elapsed we add two new rings to the array.
Finally we calculate when to call loop again so that it fires after the correct interval.
See the demo: http://jsfiddle.net/LAr76/
Related
I am making a simple game in HTML Canvas. As a part of it, in the background i want to make falling stars which create the illusion of travelling. After the star reaches the end of the canvas i want to remove it. Each star is an instance of Star class, and depending on it's radius it has a different velocity. This is where problems start. When using constant velocity for every star, they disappear one by one like they should be. When velocity is changed, stars that "overtake" slower stars, when reached the end of the canvas, do not only dissapear themselves but also remove every star that was in array before them.
I have tried many solutions described below:
let canvas = document.querySelector("canvas");
let c = canvas.getContext('2d');
Star Class declaration:
class Star{
constructor(x, y, radius, color, velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw(){
c.globalCompositeOperation='destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
c.fillStyle = this.color;
c.shadowColor= "white"
c.shadowBlur=12
c.fill();
c.shadowBlur=0
}
update(){
this.draw();
this.y = this.y + this.radius/2;
}
}
Creating stars and adding it to array
let stars = [];
function createStar(){
setInterval(()=>{
//Create random radius and X position
let randomRadius = Math.floor((Math.random()*5))
let randomXPosition = Math.floor((Math.random()*780)+15)
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white",velocity));
console.log("stars:"+ stars.length);
},300)
}
Below here I use a function calling itself to clear and refresh the star drawing. I have tried looping through stars array with forEach method, reversed loop (like in example below), I tried putting the if statement with splice function in seperate setTimeout(()=>{},0) or setTimeout(()=>{},10). I tried using the condition like
(forEach method removes stars, however the number of active stars do not remain more or less the same. It constantly slowly increases)
function animate(){
c.clearRect(0, 0, canvas.width, canvas.height);
for(let i = stars.length-1; i>=0; i--){
stars[i].update()
if (stars[i].y > canvas.height +stars[i].radius ){
stars.splice(stars[i], 1)
}
}
requestAnimationFrame(animate);
}
animate();
createStar();
I tried using the condition like:
if (stars[i].y > 5000 ){
stars.splice(stars[i], 1)
}
But it's not how it's supposed to be solved, because stars live for 5000 pixels longer,what makes game laggy.
Just to be clear on the problem.
If i generate 5 stars every 300ms and push the into the array called "stars" I get e.g.
[star1, star2, star3, star4, star5]
Lets say star1 and star 2 have small radius and they move slowly. Star3 is bigger therefore it moves faster and overtakes first two. When star3 reaches canvas.height + star[i].radius it disappear just over the canvas, but it also makes every star in array that was before star3 disappear (in this case it's star1 and star2).
This is my first post on stackoverflow. I apologise for all understatements in advance.
HTML Canvas
<body>
<div class="container">
<button class="logOutButton">Log Out</button>
<button class="topScores">Top 10</button>
<header>
<p class="hello">Hello <span id="spanName"></span></p>
<p class="topScore">Your TOP score: <span id="score"></span></p>
</header>
<div class="gameTitle">
<h2 class="title">Space Warrior</h2>
</div>
<canvas class="canvas" width="800" height="500"></canvas>
</div>
<script src="User.js"></script>
</body>
EDIT
I changed stars.splice(stars[i], 1) to stars.splice(i, 1) - not working
I tried adding another removal array like below but array stars just slowly gets bigger (even though some elements get removed)
var removeStar = [];
// stars.forEach((star,starIndex)=>{
let total = stars.length-1;
for(let i = total; i>=0; i--){
stars[i].update();
if (stars[i].y > canvas.height + stars[i].radius){
removeStar.push(i);
}
};
for(let i = removeStar.length; i>0; i--){
stars.splice(removeStar[i-1],1)
}
you wrote:
stars.splice(stars[i], 1)
first argument should be index which you want to remove ...just index stars.splice(i, 1)
..another problem is that you changing the array while looping within it, which is bad idea.
see this answer to very similar question:
https://stackoverflow.com/a/65725703/3054380
After fixing the bug mentioned above and adding minVelocity and star limiting condition (because you adding with interval and removing when star goes off the canvas - we need to limit in case interval goes faster) ...now everything looks good - working snippet below (added maxStars minVelocity)
let canvas = document.querySelector(".mycanvas");
let c = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 150;
let maxStars = 60;
let minVelocity = 0.5;
let stars = [];
class Star {
constructor(x, y, radius, color, velocity) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw() {
c.globalCompositeOperation = 'destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.shadowColor = "white"
c.shadowBlur = 12
c.fill();
c.shadowBlur = 0
}
update() {
this.draw();
this.y = this.y + Math.max(minVelocity, this.velocity * this.radius / 2);
}
}
function createStar() {
if (stars.length < maxStars) {
//Create random radius and X position
let randomRadius = Math.floor((Math.random() * 5));
let randomXPosition = Math.floor((Math.random() * (canvas.width - 20)) + 10);
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white", velocity));
console.log("stars:" + stars.length);
}
}
function animate() {
c.clearRect(0, 0, canvas.width, canvas.height);
for (let i = stars.length - 1; i >= 0; i--) {
stars[i].update()
if (stars[i].y > canvas.height) {
stars.splice(i, 1)
}
}
requestAnimationFrame(animate);
}
setInterval(createStar, 50);
animate();
.mycanvas {
width: 300px;
height: 150px;
border: 1px solid #f00;
}
<body style="background-color:#000;color:#fff;">
<div class="container">
<canvas class="mycanvas"></canvas>
</div>
<script src="User.js"></script>
</body>
I think I found the issue causing your everexpanding array. It has nothing to do with your splice function, which is implemented incorrectly as webdev-dan mentioned in his answer.
follow this logic
The radius has been set with Math.floor((Math.random()*5)).This means the radius can be 0.
In your update method you are increasing y based on the radius in this.y = this.y + this.radius/2. So this is possibly changing y with 0.
In if (stars[i].y > 5000 ) you are removing values of y over 5000.
So if the radius is 0, y doesn't change, stars are never removed. Nor can you see them visually.
solution
You could guarantee a minimal speed of 1. this.y += Math.max(1, this.radius/2).
PS: I had to do quite a bit of refactoring to figure this out. You are writing your code in a too complex way. Several types of logic are mixed up.
You really want to separate out your rendering logic from management of the stars object.
It is also quite hard to mentally keep track of the stars array because you are modifying it from anywhere; inside for loops, with async code ( the interval ).
This is what I ended up while trying to clean up your code a bit: https://jsfiddle.net/5hk0vscg/1/ Hope it's useful. Note that it is not a full cleanup, but it's an improvement over your current code.
//making custom function for a fish
drawFish=function (centerX,centerY){
//using draw function for animation
draw=function(){
var bodyLength = 115;
var bodyHeight = 74;
var bodyColor = color(162, 0, 255);
noStroke();
fill(bodyColor);
// body
ellipse(centerX, centerY, bodyLength, bodyHeight);
// tail
var tailWidth = bodyLength/4;
var tailHeight = bodyHeight/2;
triangle(centerX-bodyLength/2, centerY,
centerX-bodyLength/2-tailWidth, centerY-tailHeight,
centerX-bodyLength/2-tailWidth, centerY+tailHeight);
// eye
fill(33, 33, 33);
ellipse(centerX+bodyLength/4, centerY, bodyHeight/5, bodyHeight/5);
centerX++;
};
};
//calling custom function
drawFish(146,208);
drawFish(207,212);
drawFish(305,306);
drawFish(114,309);
drawFish(300,100);
I am trying to make a simple animation of 5 fishes in a tank moving horizontally. I wrote the code for one fish and then I make it a custom function so I can call it as many times as I want. Then I put the
draw =function(){} for animation to move the fishes. This code works until I put the draw() function into it otherwise only one fish remains and others disappear. How can we use the draw function to make an animation inside this custom function so that it works on every fish.
You can try using the operator new. I am not entirely sure if it will work in the way you coded it, but you can do something like:
function drawFish(centerX, centerY) {
this.centerX = centerX;
this.centerY = centerY;
// draw your fish here using centerX and centerY as it's X and Y
}
var fish = new drawFish(0,0);
var fish1 = new drawFish(50,50);
function Loop() {
fish.centerX++ ; fish.centerY++ ;
fish1.centerX++; fish1.centerY++;
console.log('Fish position:', fish.centerX, fish.centerY)
console.log('Fish1 position:',fish1.centerX, fish1.centerY);
}
setInterval(Loop, 500)
I want to make a box to move as a sinusoidal graph.
At the point where i am now i simply can't represent the box into the canvas. At the beginning I was able to, but after working out the trigonometry part the box disappeared and a get no error...
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="300" style="background-color:red"></canvas>
<script type="text/javascript">
var canvas = document.querySelector("canvas");//isoute me document.getElementsByTagName()
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
var x,y;
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI, 3/4*Math.PI,
5/6*Math.PI, Math.PI, 7/6*Math.PI, 5/4*Math.PI, 4/3*Math.PI, 3/2*Math.PI,
5/3*Math.PI, 7/4*Math.PI, 11/6*Math.PI, 2*Math.PI];
for (var i=0, len=x["length"]; i<len; i++){
var A;
A = Math.sin(x[i]);
y.push(A);
}console.log(y);console.log(x);
return{
x:x,
y:y
};
}
var Point = {
location : {x:0, y: can_height/2},//initial location
velocity : new PVector(x,y),
display : ctx.fillRect(can_width/2,can_height/2 , 25, 25),//initial position of the box
step : function(){
this.location.x += this.velocity.x;
this.location.y += this.velocity.y;
},
display : function(){
ctx.fillRect(this.location.x, this.location.y, 25, 25);
}
};
function update(){
Point["step"]();
ctx.clearRect(0,0, can_width, can_height);
Point["display"]();
window.setTimeout(update, 1000/30);
}
function init(){
update();
}
init();
</script>
</body>
Problem
In your PVector object you are returning Arrays for x and y, while you use them as values in the step() method. This will cause the entire array to be added as a string.
Solution
You need something that traverse that array. Here is an example, it may not be the result you're after, but it shows the principle which you need to apply:
// first note the return type here:
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI,
...snipped---
return {
x:x, // x and y are arrays
y:y
};
}
var Point = {
location: {
x: 0,
y: can_height / 2,
step: 0 // some counter to keep track of array position
}, //initial location
velocity: new PVector(x, y),
step: function () {
this.location.step++; // increase step for arrays
// using modulo will keep the step as a valid value within the array length:
// if step = 7 and length = 5, index will become 2 (sort of "wrap around")
var indexX = this.location.step % this.velocity.x.length;
var indexY = this.location.step % this.velocity.y.length
this.location.x += this.velocity.x[indexX];
this.location.y += this.velocity.y[indexY];
},
...
Updated fiddle
Tip: I would as Robin in his answer, recommend to simplify the sinus calculation. Sinus-tables are good when performance is needed and the browser can't keep up (ie. will thousands of objects), but in simpler scenario, direct calculation will work too.
If your goal is just to have a box moving in a sinusoidal graph, it can be done simpler.
This jsfiddle shows a slightly simpler example of a box moving in a sinusoidal graph where I just removed parts of your code and calculate the path with Math.sin and use time instead of precalculated values for x.
function update(){
time += 0.1;
ctx.clearRect(0,0, can_width, can_height);
x = time;
y = (can_height/2)+(can_height/2)*Math.sin(time);
console.log(x, y);
ctx.fillRect(x*16, y, 25, 25);
window.setTimeout(update, 1000/30);
}
The variables are modified to make it look ok on the canvas. You can edit the addition to time, and the altitude and base line for y, to fit your needs.
If you need to follow the specification in your code, look at the answer by Ken.
Since you want a sinusoidal move, it makes sense to use... the sin function.
The formula for a sinusoidal move is :
y = maxValue * sin ( 2 * PI * frequency * time ) ;
where the frequency is in Herz (== 'number of times per second') and time is in second.
Most likely you'll use Date.now(), so you'll have a time in millisecond, that you need to convert. Since the value of PI should not change in the near future, you can compute once the magic number
k = 2 * PI / 1000 = 0.006283185307179587
and the formula becomes :
y = sin( k * frequency * Date.now() );
Here's a simple example on how to use the formula :
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
// magic number
var k = 0.006283185307179587;
// oscillations per second
var frequency = 1/5;
// ...
var amplitude = can_width / 8;
function animate() {
ctx.clearRect(0,0,can_width, can_height);
ctx.fillStyle= '#000';
var y = amplitude * Math.sin ( k * frequency * Date.now() );
ctx.fillRect(can_width/2, can_height/2 + y, 20, 20 );
}
setInterval(animate, 30);
<canvas id="canvas" width="400" height="200" style="background-color:red"></canvas>
So it has been a good long while since I programmed in a functional language. I have this code functioning normally; however I dislike it due to my OOD preferences.
var canvasWidth = 900;
var canvasHeight = 200;
var canvas0;
var context0;
var x0 = 20;
var y0 = 20;
var dx0 = 4;
var dy0 = 4;
function draw() {
context0.clearRect(0, 0, context0.canvas.width, context0.canvas.height);
context0.beginPath();
context0.fillStyle = "red";
context0.arc(x0, y0, 20, 0, 2 * Math.PI, true);
context0.closePath();
context0.fill();
// Boundary Logic
if (x0 < 13 || x0 > context0.canvas.width - 13) {
dx0 = (-dx0);
}
if (y0 < 13 || y0 > context0.canvas.height - 13) {
dy0 = (-dy0);
}
x0 += dx0;
y0 += dy0;
}
function init() {
'use strict';
canvas0 = document.getElementById("gfxCanvas");
context0 = canvas0.getContext('2d');
context0.canvas.width = canvasWidth;
context0.canvas.height = canvasHeight;
setInterval(draw, 10);
}
I have tried to refactor it into more object oriented design but I am having problems with the graphics processing. I can get the ball to appear once but I can not get it to move. Here is my refactored code; be aware that it is in a mid point of refactoring so there are some clear errors due to random tinkering.
function Ball(x, y, r, color) {
this.radius = r;
this.x = x;
this.y = y;
this.color = color;
console.log("x in creation" + this.x);
console.log("y in creation" + this.y);
draw();
}
Ball.prototype.draw = function(){
context1.beginPath();
console.log("x in DRAW()" + this.x);
console.log("y in DRAW()" + this.y);
context1.fillStyle = this.color;
context1.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, true);
context1.closePath();
context1.fill();
};
Ball.prototype.move = function(dx, dy){
// Boundary Logic
if (this.x < 13 || this.x > context1.canvas.width - 13) {
dx = (-dx);
}
if (this.y < 13 || this.y > context1.canvas.height - 13) {
dy = (-dy);
}
this.x += dx;
this.y += dy;
};
function initialize() {
canvas1 = document.getElementById("gfxCanvas2");
context1 = canvas1.getContext('2d');
context1.canvas.width = 900;
context1.canvas.height = 200;
ball1 = new Ball(20,20,20, "red");
setInterval(ball1.move(4,4), 10);
}
I would preferably like this method to be the movement method. The actual method would take the direction/speed vectors.
setInterval(ball1.move(4,4), 10);
setInterval(ball1.move(4,4), 10);
This doesn't work the way you intended it: It calls ball1.move(4,4) once, then calls the result of that every 10ms. You want the move method to be called every 10ms instead, right? There are two ways to do that:
setInterval(function() {
ball1.move(4,4);
}, 10);
or like this (more elegant in my opinion):
setInterval(ball1.move.bind(ball1,4,4), 10);
You can use bind:
setInterval(ball1.move.bind(ball1, 4, 4), 10);
That is equivalent of wrapping your call to move in an anonymous function:
setInterval(function() { ball1.move(4, 4); }, 10);
Then you will also need to update move so that it calls draw appropriately too.
In addition, I would not use a global variable to access the drawing context - even if I wasn't going to go completely OOP I would make sure that the draw method and the move method take a context (which, for the sake of simplicity could be "owned" by the ball).
Thanks for all the help, everyone. You clarified everything very well and pointed me in the correct direction. I suspected it was working in the manner you articulated however I wasn't entirely sure. I knew a couple of things where wrong with my implementation but couldn't put it so succinctly with my current knowledge.
However, I discovered my problem which your solutions were remedying in a more direct manner. I can't treat javascript with OOD paradigms. I will be refactoring the code using a more functional design pattern. Not attempting to coerce the code into a OO design will make things considerably easier. Your solutions helped but the bounds checking code was the next problem I ran into.
I'l be working this into a module design pattern for the ball objects which should be much more suited for js scope/closures and procedural workflow.
I have a black canvas with things being drawn inside it. I want the things drawn inside to fade to black, over time, in the order at which they are drawn (FIFO). This works if I use a canvas which hasn't been resized. When the canvas is resized, the elements fade to an off-white.
Question: Why don't the white specks fade completely to black when the canvas has been resized? How can I get them to fade to black in the same way that they do when I haven't resized the canvas?
Here's some code which demonstrates. http://jsfiddle.net/6VvbQ/35/
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 150),
2, 2);
context.fillStyle = 'rgba(0,0,0,.02)';
context.fillRect(0, 0, 300, 150);
setTimeout('draw()', 1000 / 20);
}
setTimeout('draw()', 1000 / 20);
The problem is two-parted:
There is a (rather known) rounding error when you draw with low alpha value. The browser will never be able to get the resulting mix of the color and alpha channel equal to 0 as the resulting float value that is mixed will be converted to integer at the time of drawing which means the value will never become lower than 1. Next time it mixes it (value 1, as alpha internally is a value between 0 and 255) will use this value again and it get rounded to again to 1, and forever it goes.
Why it works when you have a resized canvas - in this case it is because you are drawing only half the big canvas to the smaller which result in the pixels being interpolated. As the value is very low this means in this case the pixel will turn "black" (fully transparent) as the average between the surrounding pixels will result in the value being rounded to 0 - sort of the opposite than with #1.
To get around this you will manually have to clear the spec when it is expected to be black. This will involve tracking each particle/spec yourselves or change the alpha using direct pixel manipulation.
Update:
The key is to use tracking. You can do this by creating each spec as a self-updating point which keeps track of alpha and clearing.
Online demo here
A simple spec object can look like this:
function Spec(ctx, speed) {
var me = this;
reset(); /// initialize object
this.update = function() {
ctx.clearRect(me.x, me.y, 1, 1); /// clear previous drawing
this.alpha -= speed; /// update alpha
if (this.alpha <= 0) reset(); /// if black then reset again
/// draw the spec
ctx.fillStyle = 'rgba(255,255,255,' + me.alpha + ')';
ctx.fillRect(me.x, me.y, 1, 1);
}
function reset() {
me.x = (ctx.canvas.width * Math.random())|0; /// random x rounded to int
me.y = (ctx.canvas.height * Math.random())|0; /// random y rounded to int
if (me.alpha) { /// reset alpha
me.alpha = 1.0; /// set to 1 if existed
} else {
me.alpha = Math.random(); /// use random if not
}
}
}
Rounding the x and y to integer values saves us a little when we need to clear the spec as we won't run into sub-pixels. Otherwise you would need to clear the area around the spec as well.
The next step then is to generate a number of points:
/// create 100 specs with random speed
var i = 100, specs = [];
while(i--) {
specs.push(new Spec(ctx, Math.random() * 0.015 + 0.005));
}
Instead of messing with FPS you simply use the speed which can be set individually per spec.
Now it's simply a matter of updating each object in a loop:
function loop() {
/// iterate each object
var i = specs.length - 1;
while(i--) {
specs[i].update(); /// update each object
}
requestAnimationFrame(loop); /// loop synced to monitor
}
As you can see performance is not an issue and there is no residue left. Hope this helps.
I don't know if i have undertand you well but looking at you fiddle i think that, for what you are looking for, you need to provide the size of the canvas in any iteration of the loop. If not then you are just taking the initial values:
EDIT
You can do it if you apply a threshold filter to the canvas. You can run the filter every second only just so the prefromanece is not hit so hard.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0,0,300,150);
//context.globalAlpha=1;
//context.globalCompositeOperation = "source-over";
var canvas2 = document.getElementById('canvas2');
var context2 = canvas2.getContext('2d');
canvas2.width=canvas2.height=canvas.width;
window.draw = function(){
var W = canvas2.width;
var H = canvas2.height;
context2.fillStyle='rgba(255,255,255,1)';
context2.fillRect(
Math.floor(Math.random()*W),
Math.floor(Math.random()*H),
2,2);
context2.fillStyle='rgba(0,0,0,.02)';
context2.fillRect(0,0,W,H);
context.fillStyle='rgba(0,0,0,1)';
context.fillRect(0,0,300,150);
context.drawImage(canvas2,0,0,300,150);
setTimeout('draw()', 1000/20);
}
setTimeout('draw()', 1000/20);
window.thresholdFilter = function () {
var W = canvas2.width;
var H = canvas2.height;
var i, j, threshold = 30, rgb = []
, imgData=context2.getImageData(0,0,W,H), Npixels = imgData.data.length;
for (i = 0; i < Npixels; i += 4) {
rgb[0] = imgData.data[i];
rgb[1] = imgData.data[i+1];
rgb[2] = imgData.data[i+2];
if ( rgb[0] < threshold &&
rgb[1] < threshold &&
rgb[2] < threshold
) {
imgData.data[i] = 0;
imgData.data[i+1] = 0;
imgData.data[i+2] = 0;
}
}
context2.putImageData(imgData,0,0);
};
setInterval("thresholdFilter()", 1000);
Here is the fiddle: http://jsfiddle.net/siliconball/2VaLb/4/
To avoid the rounding problem you could extract the fade effect to a separate function with its own timer, using longer refresh interval and larger alpha value.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 300),
2, 2);
setTimeout('draw()', 1000 / 20);
}
window.fadeToBlack = function () {
context.fillStyle = 'rgba(0,0,0,.1)';
context.fillRect(0, 0, 300, 300);
setTimeout('fadeToBlack()', 1000 / 4);
}
draw();
fadeToBlack();
Fiddle demonstrating this: http://jsfiddle.net/6VvbQ/37/