I am new to html5 canvas game dev and I am having a probably newbie problem.
I am making a tile based map that is supposed to turn a 2d array into a map with walls and open space, but whenever I open the game it just doesn't show up...
I don't even get errors!?
Please help me ( I am using chrome BTW )
pastebin code: http://pastebin.com/5GcQwCVa#
// Declares global variables
var canvas = document.createElement("canvas");
c = canvas.getContext("2d"),
make = {},
maps = {},
width = 800,
height = 600;
// Creates the requestAnimationFrame variable
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
}) ();
// Modifies the canvas' properties
canvas.width = width,
canvas.height = height;
// 2D arrays that make up maps
maps = {
one: [
["w","w","w","w","w","w","w","w"],
["w","o","o","o","o","o","o","w"],
["w","o","w","w","w","w","o","w"],
["w","o","w","o","o","o","o","w"],
["w","o","w","o","w","o","o","w"],
["w","o","w","o","o","w","o","w"],
["w","o","o","o","o","o","o","w"],
["w","w","w","w","w","w","w","w"]
],
two: [
["w","w","w","w","w","w","w","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","w","w","w","w","w","w","w"]
]
};
// Maps drawing functions
make = {
map: function ( map2d ) {
var i,
j,
tile,
tilesX = 8,
tilesY = 8;
for (i = 0; i < tilesX; i++) {
for(j = 0; j < tilesY; j++) {
if (map2d[i][j] === "w") {
this.tile(i * 64, j * 64);
}
}
}
},
tile: function (x, y, TD) {
switch (TD) {
case "w":
c.rect(x, y, 64, 64);
c.fillStyle = wallColor;
c.fill();
c.lineWidth = 8;
c.strokeStyle = "black";
c.stroke();
break;
case "o":
c.rect(x, y, 64, 64);
c.fillStyle = "white";
c.fill();
c.lineWidth = 8;
c.strokeStyle = "white";
c.stroke();
break;
}
}
}
// Updates constantly
function update () {
c.clearRect(0, 0, width, height);
make.map(maps.two);
requestAnimationFrame(update);
}
// Begins updating when window is ready
window.addEventListener("load", function () {
update();
});
So there are a few things. The first is you need to actually add the canvas to the document, you can do that like so.
document.body.appendChild(canvas);
I added this to your windows load event listener.
The next thing is you aren't passing "o" or "w" to your function for the switch statement to be called. So I just hard coded w for now because you have this bit
if (map2d[i][j] === "w") {
this.tile(i * 64, j * 64, "w");
}
So you are only calling draw if its a wall anyway.
After that you still see nothing because you have a variable called wallcolor that doesn't actually exist, so I changed your fill to just use black for now.
c.beginPath();
c.rect(x, y, 64, 64);
c.fillStyle = "black";
c.fill();
c.lineWidth = 8;
c.strokeStyle = "black";
c.stroke();
c.closePath();
Another thing you will notice is the addition of beginPath and closePath if you are creating paths you need to use these otherwise all your shapes will keep being added to the same path and every time you call fill or stroke it will fill or stroke everything you've already drawn making it really slow over time. The following is a good explanation of what paths are
Live Demo
// Declares global variables
var canvas = document.createElement("canvas");
c = canvas.getContext("2d"),
make = {},
maps = {},
width = 800,
height = 600;
// Creates the requestAnimationFrame variable
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
}) ();
// Modifies the canvas' properties
canvas.width = width,
canvas.height = height;
// 2D arrays that make up maps
maps = {
one: [
["w","w","w","w","w","w","w","w"],
["w","o","o","o","o","o","o","w"],
["w","o","w","w","w","w","o","w"],
["w","o","w","o","o","o","o","w"],
["w","o","w","o","w","o","o","w"],
["w","o","w","o","o","w","o","w"],
["w","o","o","o","o","o","o","w"],
["w","w","w","w","w","w","w","w"]
],
two: [
["w","w","w","w","w","w","w","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","o","o","o","o","o","o","w"],
["w","w","w","w","w","w","w","w"]
]
};
// Maps drawing functions
make = {
map: function ( map2d ) {
var i,
j,
tile,
tilesX = 8,
tilesY = 8;
for (i = 0; i < tilesX; i++) {
for(j = 0; j < tilesY; j++) {
if (map2d[i][j] === "w") {
this.tile(i * 64, j * 64, "w");
}
}
}
},
tile: function (x, y, TD) {
switch (TD) {
case "w":
c.beginPath();
c.rect(x, y, 64, 64);
c.fillStyle = '#000';
c.fill();
c.lineWidth = 8;
c.strokeStyle = "black";
c.stroke();
c.closePath();
break;
case "o":
c.rect(x, y, 64, 64);
c.fillStyle = "white";
c.fill();
c.lineWidth = 8;
c.strokeStyle = "white";
c.stroke();
break;
}
}
}
// Updates constantly
function update () {
c.clearRect(0, 0, width, height);
make.map(maps.two);
requestAnimationFrame(update);
}
// Begins updating when window is ready
window.addEventListener("load", function () {
// Add the canvas
document.body.appendChild(canvas);
update();
});
Related
I have a blue circle which is rotating around the red circle and moves on canvas continuously in one direction as long as the button is pressed.
Now I want to draw with the red circle while it is moving when the button is pressed (trace of its path).
Problems:
I have tried to make changes to clearRect() but I didn't succeed. the blue circle starts to draw on the canvas while moving which I don't need.
If its not possible to do with clearRect() function, Is it possible to do this by stacking canvas layers. Please help with example
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let positionX = 100;
let positionY = 100;
let X = 50;
let Y = 50;
let angle = 0;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function circle(){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, 20, 0, Math.PI*2);
ctx.closePath();
ctx.fill();
}
function direction(){
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(positionX + X, positionY + Y, 10, 0, Math.PI*2);
ctx.closePath();
positionX = 35 * Math.sin(angle);
positionY = 35 * Math.cos(angle);
ctx.fill();
}
function animate(){
if (mouseButtonDown) {
X += positionX / 10;
Y += positionY / 10;
} else {
angle += 0.1;
}
ctx.clearRect(X-positionX,Y-positionY, 20, 20);
circle();
direction();
requestAnimationFrame(animate);
}
animate();
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="canvas1"></canvas>
<script src="script.js"></script>
</body>
</html>
Don`t stack canvas on the page
Each canvas you add to the page increases the amount of work the GPU and page compositor needs to do to render the page.
Use a second canvas that is not on the page and do the compositing by rendering the canvas to the onpage canvas using ctx.drawImage(secondCanvas, 0, 0).
This reduces the workload for the compositor, and in many cases avoid the need to do an addition image render (composite) for the second canvas I.E. onpage can require 3 drawImages (one for each canvas and once for the result) rather than 2 (once in your code and once as the result) if you use only one onpage canvas.
Using second canvas
Create a second canvas to store the drawn red lines.
You can create a copy of a canvas using
function copyCanvas(canvas, copyContent = false) {
const can = Object.assign(document.createElement("canvas"), {
width: canvas.width, height: canvas.height
});
can.ctx = can.getContext("2d");
copyContent && can.ctx.drawImage(canvas, 0, 0);
return can;
}
When you create render functions like circle, and direction pass as an argument the 2D context eg circle(ctx) so that it is easy to direct the rendering to any canvas.
function circle(ctx){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, redSize, 0, Math.PI*2);
ctx.fill();
}
// the background canvas
const bgCan = copyCanvas(canvas);
circle(bgCan.ctx); // will draw to the background canvas
Updating animation
When animating is is easiest to clear the whole canvas rather than mess about clearing only rendered pixels. Clearing rendered pixels gets complicated very quickly and will end up being many times slower than clearing the whole canvas.
After you clear the canvas draw the background canvas to the main canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bgCan, 0, 0);
When the mouse button is down draw the circle to the background canvas and while it is up draw to the main canvas.
Example
Adds a function to copy a canvas. copyCanvas
Clears the main canvas, and draws the background canvas onto the main canvas.
Render functions circle and direction have argument ctx to direct rendering to any context.
When mouse is down circle is drawn to background canvas bgCan else to the main canvas.
requestAnimationFrame(animate);
const ctx = canvas1.getContext('2d');
canvas1.width = innerWidth;
canvas1.height = innerHeight;
const bgCan = copyCanvas(canvas1);
const redSize = 10, blueSize = 5; // circle sizes on pixels
const drawSpeed = 2; // when button down draw speed in pixels per frame
var X = 50, Y = 50;
var angle = 0;
var mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function copyCanvas(canvas) {
const can = Object.assign(document.createElement("canvas"), {
width: canvas.width, height: canvas.height
});
can.ctx = can.getContext("2d");
return can;
}
function circle(ctx){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, redSize, 0, Math.PI*2);
ctx.fill();
}
function direction(ctx){
const d = blueSize + redSize + 5;
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(d * Math.sin(angle) + X, d * Math.cos(angle) + Y, blueSize, 0, Math.PI*2);
ctx.fill();
}
function animate(){
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bgCan, 0, 0);
if (mouseButtonDown) {
circle(bgCan.ctx);
X += Math.sin(angle) * drawSpeed;
Y += Math.cos(angle) * drawSpeed;
} else {
angle += 0.1;
circle(ctx);
}
direction(ctx);
requestAnimationFrame(animate);
}
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<canvas id="canvas1"></canvas>
BTW ctx.closePath() is like ctx.lineTo it is not the opposite to ctx.beginPath. A full arc or if you are just filling a shape you don't need to use ctx.closePath
BTW window is the default this, you don't need to include it, you dont use it to get at window.documentso why use it forwindow.innerWidth(same asinnerWidth` )
You could alter your code to keep track of the path of the red circle, with an array property, like this:
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function drawCircle({x, y, radius, color}) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2);
ctx.fill();
}
const red = { x: 50, y: 50, radius: 20, color: "red", path: [] };
const blue = { x: 100, y: 100, radius: 10, color: "blue", angle: 0 };
function animate(){
if (mouseButtonDown) {
red.path.push({x: red.x, y: red.y}); // store the old value
red.x += (blue.x - red.x) / 10;
red.y += (blue.y - red.y) / 10;
} else {
blue.angle += 0.1;
}
blue.x = red.x + 35 * Math.sin(blue.angle);
blue.y = red.y + 35 * Math.cos(blue.angle);
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear the whole canvas
for (const {x, y} of red.path) { // draw circle at all the previous positions
drawCircle({...red, x, y});
}
drawCircle(red);
drawCircle(blue);
requestAnimationFrame(animate);
}
animate();
Using 2 canvases also works and may perform better especially when the path of the red circle has gotten long, because the background canvas doesn't need to be cleared and redrawn. Add a 2nd canvas in your html page with the same positioning, and give them ids 'background' and 'foreground'. You can then adjust the code to draw the blue circle to the foreground and red circles to the background (or vice versa).
// Create 2 canvases, set them to full size and get the contexts
const backgroundCanvas = document.getElementById('background');
const foregroundCanvas = document.getElementById('foreground');
const background = backgroundCanvas.getContext("2d");
const foreground = foregroundCanvas.getContext("2d");
backgroundCanvas.width = innerWidth;
backgroundCanvas.height = innerHeight;
foregroundCanvas.width = innerWidth;
foregroundCanvas.height = innerHeight;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
// Create objects to represent the current properties of the red and blue circle
const red = { x: 50, y: 50, radius: 20, color: "red" };
const blue = { x: 100, y: 100, radius: 10, color: "blue", angle: 0};
function drawCircle(ctx, {x, y, radius, color}) {
//--- Draw a circle to the specified canvas context, ctx = foreground or background
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2);
ctx.closePath();
ctx.fill();
}
function animate(){
if (mouseButtonDown) {
red.x += (blue.x - red.x) / 10;
red.y += (blue.y - red.y) / 10;
} else {
blue.angle += 0.1;
}
blue.x = red.x + 35 * Math.sin(blue.angle);
blue.y = red.y + 35 * Math.cos(blue.angle);
drawCircle(background, red); // Draw the red circle in the background (without clearing the existing circles)
foreground.clearRect(0, 0, foregroundCanvas.width, foregroundCanvas.height); // Clear the foreground
drawCircle(foreground, blue); // Draw the blue circle on the foreground
requestAnimationFrame(animate);
}
animate();
Either way, it's convenient to abstract out the circle drawing code into a function or method, and to store the properties of the two circles in objects.
As #Blindman67's answer notes, there may be a performance cost of stacking 2 canvases, and if that is an issue you may want to try drawing the background offscreen then copying it to the on-screen canvas.
If you're not opposed to just building a particle class you can do it using them. In the snippet below I have a Circle class and a Particles class to creat what you are trying to achieve. I currently have the particles max at 500 but you can change it or delete that line all together if you ne er want them gone.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let mouseButtonDown = false;
//the array holding particles
let particles = [];
//the counter is only needed it you want to slow down how fast particles are being pushed and dispolayed
let counter = 0;
document.addEventListener("mousedown", () => (mouseButtonDown = true));
document.addEventListener("mouseup", () => (mouseButtonDown = false));
//ES6 constructor class
class Circle {
//sets the basic structor of the object
constructor(r, c) {
this.x = 100;
this.y = 100;
this.x2 = 50;
this.y2 = 50;
this.r = r; //will be assigned the argument passed in through the constructor by each instance created later
this.color = c; //same as above. This allows each instance to have different parameters.
this.angle = 0;
}
//this function creates the red circle
drawRed() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
//this function creates the blue circle
drawBlue() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x + this.x2, this.y + this.y2, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
//this function is where we'll place parameter that change our object
update() {
//makes the blue circle rotate
this.x2 = 35 * Math.sin(this.angle);
this.y2 = 35 * Math.cos(this.angle);
//mouse action is same as your code
if (mouseButtonDown) {
this.x += this.x2 / 20;
this.y += this.y2 / 20;
} else {
this.angle += 0.1;
}
}
}
//When using this type of constructor class you have to create an instance of it by calling new Object. You can create as money as you want.
let blueCircle = new Circle(10, "blue"); //passing in the radius and color in to the constructor
let redCircle = new Circle(20, "red");
//another class for the particles
class Particles {
constructor() {
this.x = redCircle.x;
this.y = redCircle.y;
this.r = redCircle.r;
this.color = redCircle.color;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
//just wrapping all of the particle stuff into one function
function handleParticles() {
//while the mouse is held it will push particles
if (mouseButtonDown) {
particles.push(new Particles());
}
//this loops through the array and calls the draw() function for each particle
for (let i = 0; i < particles.length; i++) {
particles[i].draw();
}
//this keeps the array from getting too big.
if (particles.length > 500) {
particles.shift();
}
}
//wrap all functions into this one animate one and call requeatAnimationFrame
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
handleParticles();
//These must be called for each instance created of the object
blueCircle.drawBlue();
blueCircle.update();
redCircle.drawRed();
redCircle.update();
requestAnimationFrame(animate);
}
animate();
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<canvas id="canvas"></canvas>
I'd also like to add you can change the rate that the particles are drawn by adding a counter variable and then limiting the draw like counter % 10 == 0
EXAMPLE
add global variable let counter = 0;
then in the handleParticles function add this
function handleParticles() {
counter++
if (mouseButtonDown && counter % 10 == 0) {
particles.push(new Particles());
}
for (let i = 0; i < particles.length; i++) {
particles[i].draw();
}
if (particles.length > 500) {
particles.shift();
}
}
how can I fill the "new" canvas circle that appears next to the older one.
There is no problem with rectangle for example:
**
ctx.fillStyle = 'rgba('+quadratto.r+','+quadratto.g+','+quadratto.b+',1)';
quadratto.x += quadratto.speedX;
quadratto.y += quadratto.speedY;
quadratto.speedY += quadratto.speedY*(-0.15);
ctx.fillRect(quadratto.x-quadratto.h/4, quadratto.y-quadratto.h/2, 2, 2);**
What I want to do?
I'm creating animation in canvas where random-sized-color circle will appear and
it will move in a specified direction. The new canvas layaer will appear in the next frame (fps) with a new(old) circle.
var myCanvasPattern = document.createElement('canvas');
myCanvasPattern.width = window.innerWidth;
myCanvasPattern.height = window.innerHeight;
document.body.appendChild(myCanvasPattern);
var ryC = myCanvasPattern.getContext('2d');
function lottery(min, max) {
return Math.floor(Math.random()*(max-min+1))+min;
}
var allQuadro = [];
var fps = 50;
var lastTime = 0;
animationLoop();
function animationLoop(time){
requestAnimationFrame( animationLoop );
if(time-lastTime>=1000/fps){
lastTime = time;
for(var i=0;i<10;i++){
allQuadro.push({
r : lottery(0, 240),
g : lottery(0, 240),
b : lottery(0, 240),
circleR : lottery(10, 30),
x : myCanvasPattern.width/2,
y : myCanvasPattern.height/2,
speedX : lottery(-1000,1000)/100,
speedY : lottery(-1000,1000)/100
})
}
ryC.fillStyle = 'rgba(255,255,255,0.2)';
ryC.fill(0,0,myCanvasPattern.width, myCanvasPattern.height);
for(var i=0; i<allQuadro.length;i++){
var circle = allQuadro[i];
ryC.fillStyle = 'rgba('+circle.r+','+circle.g+','+circle.b+',1)';
circle.x += circle.speedX;
circle.y += circle.speedY;
//HERE's THE PROBLEM BELOW. HOW TO CREATE NEW ONE THAT APPEARS NEXT TO PREVIOUS ONE WITH NEW RANDOM COLOR
ryC.arc(circle.x-circle.circleR/2, circle.y-circle.circleR/2, circleR, 0, 2 * Math.PI);
//ryC.fill();
}
// ryC.fillStyle = 'rgba('+r+','+g+','+b+',1)';
//ryC.arc(x+speedX, y+speedY, circleR, 0, 2 * Math.PI);
//ryC.fill();
}
}
body {
margin: 0;
padding: 0;
overflow: hidden;
}
The fillRect() will fill directly to the canvas without going via a path (versus for example rect()).
The arc() on the other hand will add to a path which needs to be filled later. It also require the path to be cleared in-between the calls using beginPath().
A simple way to think about it is to wrap the necessary code into a function that acts like fillRect():
function fillArc() {
ctx.beginPath(); // clear current path
ctx.arc.apply(ctx, arguments); // apply arguments to arc() call
ctx.fill();
// notice that the arc still exist on the path after this call
// so to "truly" behave like fillRect() you could consider an
// additional beginPath() here.. it will depend on your code
}
In action:
var ctx = c.getContext("2d");
ctx.fillStyle = "#09f";
fillArc(70, 70, 70, 0, 6.28);
ctx.fillStyle = "#0a9";
fillArc(220, 70, 70, 0, 6.28);
function fillArc() {
ctx.beginPath();
ctx.arc.apply(ctx, arguments);
ctx.fill();
}
<canvas id=c></canvas>
If you are bold you can also add the method to the context itself before calling getContext():
CanvasRenderingContext2D.prototype.fillArc = function() {
this.beginPath();
this.arc.apply(this, arguments);
this.fill();
}
The use it like any other method:
ctx.fillArc( ... );
CanvasRenderingContext2D.prototype.fillArc = function() {
this.beginPath();
this.arc.apply(this, arguments);
this.fill();
}
var ctx = c.getContext("2d");
ctx.fillStyle = "#09f";
ctx.fillArc(70, 70, 70, 0, 6.28);
ctx.fillStyle = "#0a9";
ctx.fillArc(220, 70, 70, 0, 6.28);
<canvas id=c></canvas>
I am trying to create a template for initiating as many waterfall objects as I wish without having to create a new canvas for each of them. I want two waterfalls with different colors but it doesn't work. I can't figure out why and I'm on it since a few hours. How can I make both red and blue waterfalls appear where the first has a lower z index than the last instantiation?
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var w = canvas.width = window.innerWidth;
var h = canvas.height = window.innerHeight;
function waterfall(color) {
var self = this;
this.color = color;
this.water = [];
this.Construct = function(y, vel, acc) {
this.y = y;
this.vel = vel;
this.acc = acc;
}
for(var i = 0; i < 1000; i++) {
this.water.push(new this.Construct(Math.random() * 65, 0.1 + Math.random() * 4.3, 0));
}
this.flow = function(color) {
ctx.clearRect(0, 0, w, h);
for(var i = 0; i < this.water.length; i++) {
this.water[i].vel += this.water[i].acc;
this.water[i].y += this.water[i].vel;
ctx.beginPath();
ctx.arc(0 + i * 0.5, this.water[i].y, 2, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
for(var i = 0; i < this.water.length; i++) {
if(this.water[i].y > window.innerHeight) {
this.water[i].y = 0;
}
}
requestAnimationFrame(function() {
self.flow.call(self);
});
}
this.flow(this.color)
}
new waterfall("blue");
new waterfall("red");
Here's my working code: https://jsfiddle.net/testopia/d9jb08xb/5/
and here again my intention to create two separate waterfalls but this time with the prototype inheritance:
https://jsfiddle.net/testopia/d9jb08xb/8/
I do prefer the latter but I just cant get either working.
The problem is that you are clearing the canvas in each waterfall. One is overpainting the other. You can immediately see that by commenting out the line
ctx.clearRect(0, 0, w, h);
Of course the water smears that way.
You have to manage your waterfalls in a way that in each animation frame you first clear the canvas then let them paint all.
Here is a quick attempt using a master flow_all() function:
https://jsfiddle.net/kpomzs83/
Simply move this line...
ctx.clearRect(0, 0, w, h);
... to here...
requestAnimationFrame(function() {
ctx.clearRect(0, 0, w, h); // ensure that w and h are available here.
self.flow.call(self);
});
This ensures that you do not clear the canvas before the 2nd waterfall is drawn. This clears the canvas, then draws the two waterfalls. Make sure you've added them to your water array, of course.
I'm using Canvas to play and learn with Javascript. Currently I'm creating a circle and have it display in random areas on the screen. I was able to complete that exercise completely; everything ran smoothly in one function.
Now I would like to create an object for the circle and call it in the for loop. I created the object, but something is still wrong. I'm only seeing one circle instead of 40. I banged my head on this for awhile before coming here for help. Take a look at the code below.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (!ctx) {
alert('HTML5 Canvas is not supported in you browser');
}
function Circle(posX, posY, radius, startAngle, endAngle, anticlockwise) {
this.posX = posX;
this.posY = posY;
this.radius = radius;
this.startAngle = startAngle;
this.endAngle = endAngle;
this.anticlockwise = anticlockwise;
this.test = function() {
ctx.beginPath();
ctx.arc(posX, posY, radius, startAngle, endAngle, anticlockwise);
ctx.fill();
}
}
var cir = new Circle(
(Math.random() * canvas.width),
(Math.random() * canvas.height),
20,
0,
Math.PI*2,
true
);
function drawCircle() {
for(var i = 0; i < 40; i++){
cir.test();
}
}
/*setInterval(drawCircle, 400)*/
drawCircle();
You are calling cir.test() 40 times without having 40 instances of Circle. It is the same circle being drawn 40 times on top of itself.
This might be an immediate fix to your problem:
function drawCircle() {
for(var i = 0; i < 40; i++){
// Mind you that doing this
// Will not allow you to reference
// your circles after they are
// created. The best method is
// to put them in an array
// of circles
var cir = new Circle(
(Math.random() * canvas.width),
(Math.random() * canvas.height),
20,
0,
Math.PI*2,
true
);
cir.test();
}
}
/*setInterval(drawCircle, 400)*/
drawCircle();
However, I would recommend the following changes to your code:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (!ctx) {
alert('HTML5 Canvas is not supported in you browser');
}
function Circle(posX, posY, radius, startAngle, endAngle, anticlockwise) {
this.posX = posX;
this.posY = posY;
this.radius = radius;
this.startAngle = startAngle;
this.endAngle = endAngle;
this.anticlockwise = anticlockwise;
// Using better function names
// is always a good idea
this.testDraw = function() {
ctx.beginPath();
ctx.arc(posX, posY, radius, startAngle, endAngle, anticlockwise);
ctx.fill();
}
}
// Create an array to fill
// with Circle instances
var circlesArray = []
// Changed drawCircle to drawCircles
// it is clearer
function drawCircles() {
for(var i = 0; i < 40; i++){
// Create new Circle objects
// and add them to the circlesArray
// this will allow you to have a
// each circle later on
circlesArray.push(new Circle(
(Math.random() * canvas.width),
(Math.random() * canvas.height),
20,
0,
Math.PI*2,
true
));
// Go through each item of the array
// and call the test function
circlesArray[i].testDraw();
}
}
/*setInterval(drawCircle, 400)*/
drawCircles();
Currently your drawCircle() function is running a single test function on the same 'cir' variable 40 times. What you want to do is to fill an array with 40 new items using the for-loop. Then, use another for-loop to define those items as new Circle objects and call the test function on each new circle.
Here is the code I would use:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (!ctx) {
alert('HTML5 Canvas is not supported in you browser');
}
function Circle(posX, posY, radius, startAngle, endAngle, anticlockwise) {
this.posX = posX;
this.posY = posY;
this.radius = radius;
this.startAngle = startAngle;
this.endAngle = endAngle;
this.anticlockwise = anticlockwise;
this.test = function() {
ctx.beginPath();
ctx.arc(posX, posY, radius, startAngle, endAngle, anticlockwise);
ctx.fill();
}
}
/*Create an array to hold your circles*/
var circleArray = [];
function drawCircle() {
for (var i = 0; i < 40; i++) {
circleArray.push('cirlce' + i); /*Push circle variable into circleArray*/
}
for (var i = 0; i < circleArray.length; i++) {
/*Create a new circle object for every iteration of the circleArray*/
circleArray[i] = new Circle(
(Math.random() * canvas.width), (Math.random() * canvas.height),
20,
0,
Math.PI * 2,
true
);
circleArray[i].test(); /*Run the test function for every item in the circle array*/
}
}
/*setInterval(drawCircle, 400)*/
drawCircle();
<canvas id='canvas'></canvas>
Please read the comments, if you need more help understanding this just comment below.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (!ctx) {
alert('HTML5 Canvas is not supported in you browser');
}
//The only thing I can see perhaps changing is radius so use radius as parameter
function Circle(radius) {
this.posX = Math.random() * canvas.width; //This is always going to be the same so no need to pass as an argument
this.posY = Math.random() * canvas.height; //So will this
this.test = function() {
ctx.beginPath();
ctx.arc(this.posX, this.posY, radius, 0, Math.PI*2, true); //So will Math.PI*2, and true
ctx.fill();
}
this.test();
}
function drawCircle() {
for(var i = 0; i < 40; i++){
new Circle(i); //This passes i as the radius so you can see the differences
}
}
drawCircle();
This script is endlessly taking memory in all the browsers. I can't see why!
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var particles = [], amount = 5;
var x = 100; var y = 100;
var W, H;
var p, gradient;
//dimensions
if(window.innerHeight){
W = window.innerWidth, H = window.innerHeight;
}else{
W = document.documentElement.clientWidth, H = document.documentElement.clientHeight;
}
canvas.width = W, canvas.height = H;
//array voor de meerdere particles
for(var i=0;i<amount;i++){
particles.push(new create_particle());
}
function create_particle(){
//random positie op canvas
this.x = Math.random()*W;
this.y = Math.random()*H;
//random snelheid
this.vx = Math.random()*20-10;
this.vy = Math.random()*20-10;
//Random kleur
var r = Math.random()*255>>0;
var g = Math.random()*255>>0;
var b = Math.random()*255>>0;
this.color = "rgba("+r+", "+g+", "+b+", 0.5)";
this.radius = Math.random()*20+20;
}
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
function draw(){
canvas.width = canvas.width;
canvas.height = canvas.height;
//achtergrond tekenen
//ctx.globalCompositeOperation = "source-over";
//ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(0, 0, W, H);
//ctx.globalCompositeOperation = "lighter";
//teken cirkel
for(var t=0; t<particles.length;t++){
p = particles[t];
//gradient
gradient = ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.radius);
gradient.addColorStop(0,"white");
gradient.addColorStop(0.4,"white");
gradient.addColorStop(0.4,p.color);
gradient.addColorStop(1,"black");
ctx.beginPath();
ctx.fillStyle = gradient;
ctx.arc(p.x,p.y,p.radius,Math.PI*2,false)
ctx.fill();
//beweeg
p.x+=p.vx;
p.y+=p.vy;
//canvas boundery detect
if(p.x < -50)p.x = W+50;
if(p.y < -50)p.y=H+50;
if(p.x > W+50)p.x = -50;
if(p.y > H+50)p.y = -50;
}
}
(function animloop(){
canvas.width = canvas.width;
canvas.height = canvas.height;
requestAnimFrame(animloop);
draw();
})();
//resize?
function resizeCanvas(){
canvas.height = W;
canvas.width = H;
ctx.fillRect(0, 0, W, H);
}
if(window.addEventListener){
window.addEventListener('resize', resizeCanvas, false);
}else{
window.attachEvent('resize', resizeCanvas);
}
I tried to change some code around (this is also final version) but still it leaks. If you use this script and watch 'taskmanager' or in-browser's memory check you see that it slowly and constantly eats memory.
EDIT: after adding in the canvas.height solution and moving some declaring's around, the script still leaks! I must say that it looks like Firefox leaks harder then Chrome!
You have:
canvas.width = canvas.width;
canvas.height = canvas.height;
I'm guessing this does the same as clearRect... but to be sure try this too:
function draw(){
ctx.clearRect ( 0 , 0 , canvas.width , canvas.height );
/* draw */
}
See if any thing changes.
Try cleaning the canvas before starting another set of drawing. You can clear it by setting it's width and height again.
Here is some orientative code:
function draw() {
canvas.width = canvas.width;
canvas.height = canvas.height;
/* draw */
}
my guess is your create_particle function could be leaking but im not sure as how, one idea would be to create an interal object instead of using this
function create_particle(){
return {
x: Math.random()*W,
y: Math.random()*H,
vx: Math.random()*20-10,
vy: Math.random()*20-10,
r: Math.random()*255>>0,
g: Math.random()*255>>0,
b: Math.random()*255>>0,
color: "rgba("+r+", "+g+", "+b+", 0.5)",
radius: Math.random()*20+20,
};
}
Im not sure if this is the issue, but it seems to be the only thing I can really think of that kinda looks odd,