In the code below I am starting/stopping a flame of particles when clicking on a block. It works ok, however, I need to have about 10-20 flames able to start/stop individually and keep track of them. A friend suggested that I put each flame in a small canvas, then draw each canvas individually, which i think would be a rendering overkill, having 10-20 draw() running at the same time. How should i go about this?
One thing to note is that the position of the flame is given in particle(), then an array of particles is created, which basically represents the flame.
// init canvas
var canvas = $('canvas'),
ctx = canvas[0].getContext('2d') // world
,
ctx2 = canvas[1].getContext('2d') // fog
,
context = canvas[2].getContext('2d') // flame
,
mDown = false,
r1 = 100,
r2 = 300,
density = .4,
hideOnMove = true,
hideFill = 'rgba( 0, 0, 0, 1 )'
,
overlay = 'rgba( 0, 0, 0, 1 )',
particles = [],
particle_count = 100;
// init flame
for (var i = 0; i < particle_count; i++) {
particles.push(new particle());
}
if (!hideOnMove) {
// shouldn't be done like this, but this is a demo
canvas.get(1).remove();
}
// black out the canvas
ctx.fillStyle = overlay;
ctx.fillRect(0, 0, 1280, 800);
// set up our "eraser"
ctx.globalCompositeOperation = 'destination-out';
canvas.last()
.on('mousemove', function (ev, ev2) {
ev2 && (ev = ev2);
var pX = ev.pageX,
pY = ev.pageY;
// reveal wherever we drag
var radGrd = ctx.createRadialGradient(pX, pY, r1, pX, pY, r2);
radGrd.addColorStop(0, 'rgba( 0, 0, 0, 1 )');
radGrd.addColorStop(density, 'rgba( 0, 0, 0, .1 )');
radGrd.addColorStop(1, 'rgba( 0, 0, 0, 0 )');
ctx.fillStyle = radGrd;
ctx.fillRect(pX - r2, pY - r2, r2 * 2, r2 * 2);
// partially hide the entire map and re-reval where we are now
ctx2.globalCompositeOperation = 'source-over';
ctx2.clearRect(0, 0, 1280, 800);
ctx2.fillStyle = hideFill;
ctx2.fillRect(0, 0, 1280, 800);
var radGrd = ctx.createRadialGradient(pX, pY, r1, pX, pY, r2);
radGrd.addColorStop(0, 'rgba( 0, 0, 0, 1 )');
radGrd.addColorStop(.8, 'rgba( 0, 0, 0, .1 )');
radGrd.addColorStop(1, 'rgba( 0, 0, 0, 0 )');
ctx2.globalCompositeOperation = 'destination-out';
ctx2.fillStyle = radGrd;
ctx2.fillRect(pX - r2, pY - r2, r2 * 2, r2 * 2);
})
.trigger('mousemove', {
pageX: 150,
pageY: 150
});
function drawing() {
// clear canvas
context.clearRect(0, 0, 1280, 800);
context.globalCompositeOperation = "lighter";
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
context.beginPath();
//changing opacity according to the life.
//opacity goes to 0 at the end of life of a particle
p.opacity = Math.round(p.remaining_life / p.life * 100) / 100
//a gradient instead of white fill
var gradient = context.createRadialGradient(p.location.x, p.location.y, 0, p.location.x, p.location.y, p.radius);
gradient.addColorStop(0, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", " + p.opacity + ")");
gradient.addColorStop(0.5, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", " + p.opacity + ")");
gradient.addColorStop(1, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", 0)");
context.fillStyle = gradient;
context.arc(p.location.x, p.location.y, p.radius, Math.PI * 2, false);
context.fill();
//lets move the particles
p.remaining_life--;
p.radius--;
p.location.x += p.speed.x;
p.location.y += p.speed.y;
//regenerate particles
if (p.remaining_life < 0 || p.radius < 0) {
//a brand new particle replacing the dead one
particles[i] = new particle();
}
}
}
// set flame on/off
var myVar = 0;
var on = 0;
$('.c').css({
left: "610px",
top: "500px"
});
$('.c').click(function () {
if (on == 0) {
myVar = setInterval(drawing, 33);
on = 1;
} else {
clearInterval(myVar);
context.clearRect(0, 0, 1280, 800);
on = 0;
}
});
function particle() {
//speed, life, location, life, colors
//speed.x range = -2.5 to 2.5
//speed.y range = -15 to -5 to make it move upwards
//lets change the Y speed to make it look like a flame
this.speed = {
x: -2.5 + Math.random() * 5,
y: -15 + Math.random() * 10
};
//flame location
this.location = {
x: 640,
y: 520
};
//radius range = 10-30
this.radius = 10 + Math.random() * 20;
//life range = 20-30
this.life = 20 + Math.random() * 10;
this.remaining_life = this.life;
//colors
this.r = Math.round(Math.random() * 255);
this.g = Math.round(Math.random() * 255);
this.b = Math.round(Math.random() * 255);
}
See the full webpage here http://codepen.io/anon/pen/hxrat
You could probably increase performance if you pre-generate the particles in a bunch of small canvases. Basically generate a list of particle-images with different colors and sizes and then use those for all the particles. Opacity can still be applied when the particle image is drawn. It should be faster to just draw a small canvas at a given position with a given opacity than to draw a path with radial gradient.
Here's an example, seems to double the performance at least: http://codepen.io/anon/pen/Izqwu
I don't pre-generate particle canvases. Instead I wrote a function getParticleCanvas() which takes a color and returns a 32*32 pixel canvas (creates it once if it doesn't exist), which is then used in drawing(). The particale canvas is then drawn at the correct size and opacity. The position is rounded to the nearest pixel for performance.
Also, to reduce the number of possible particle canvases, the random colors are rounded to 8 different steps per channel:
this.r = Math.round(Math.random() * 8)*32;
this.g = Math.round(Math.random() * 8)*32;
this.b = Math.round(Math.random() * 8)*32;
You can probably decrease the radius in getParticleCanvas() from 16 to 8 without being noticeable as well.
I got some outside support and ended up doing it like this, which I think it's the best way to keep performance:
Create a small dummy canvas in memory to run the draw() of the flame
var flameCanvas = document.createElement('canvas');
flameCanvas.width = 100;
flameCanvas.height = 400;
var context = flameCanvas.getContext('2d');
Use the big canvas to display copies of the dummy canvas wherever needed.
ctx3.drawImage(flameCanvas, 420, -37, 50, 200);
ctx3.drawImage(flameCanvas, 325, -47, 60, 240);
...
The rendering runs in the dummy canvas and the image is just copied all over, which increases performance so much. The only drawback is that all the flames will be the same.
To keep track of them, I surrounded each with an if clause
if (centerFlame_on) {
ctx3.drawImage(flameCanvas, 590, 165);
}
And I update centerFlame_on to true/false based on the mouse click.
Related
How do I emulate Gravity with canvas Objects having only a few variables to work with.
I created the base canvas, game, time, score, everything, even gameStates, but I'm stuck on the part where you "add the velocity and the gravity factor into the Player Y variables".
I tried multiplying the gravity factor by a specified value, and then adding it to the yVel and then adding that to the actual Y value, but I cant translate the positioning correctly.
I think if I figured out how to "create gravity" creating jumping, moving right, and moving left wouldnt be too difficult.
Heres the main code im using to look for the platforms:
map.plates represents an array full of arrays which each contain 4 values for a plate (platform plate)
e is the map.plates.Arrays. playY is basically the player's exactly Y Height,, all rendered into fillRect();
function detectGravity() {
map.plates.forEach(e => {
if (playY > e[1] && playX <= e[0] && playX >= e[0] + e[2]) {
} else {
playY += 0; // Gravity Calculations here
}
});
}
I dont really know if I should include anything else here, but if you want the whole project, see the snippet below.
If theres anything wrong with the question, please tell me, I havent been on here in nearly half a year.
Full code incase codepen dies (suggested in comments):
"esversion: 6";
const can = document.querySelector(".block"),
ctx = can.getContext("2d"),
mScore = 100,
map = {
plates: [
[25, 25, 25, 2],
[75, 25, 25, 2],
[125, 25, 25, 2],
[175, 25, 25, 2],
[225, 25, 25, 2],
[25, 75, 25, 2],
[75, 62, 25, 2],
[125, 50, 25, 2],
[175, 38, 25, 2],
[25, 87, 25, 2],
[75, 100, 25, 2]
],
moneys: [
[25, 25],
[125, 25],
[225, 25],
[75, 62],
[75, 100]
],
player: [25, 25, 2, 2],
badSpt: []
};
let score = 0,
time = 60,
gameOn = 0;
let playX,
playY,
velX,
velY,
grav = 1.05;
can.addEventListener("click", startGame);
function startGame() {
if (gameOn != 1) {
gameOn = 1;
init();
gameTime = setInterval(() => {
if (time != 0) {
time -= 1;
}
}, 1000);
}
}
function init() {
can.width = 300;
can.height = 300;
drawEnviron();
drawLevel();
drawPlayer();
drawGame();
drawPixels();
if (time == 0) {
clearInterval(gameTime);
time = 60;
gameOn = 2;
}
window.requestAnimationFrame(init);
}
function drawEnviron() {
with (ctx) {
fillStyle = "#000000";
fillRect(0, 0, can.width, can.height);
fillStyle = "rgba(255, 255, 255, 0.5)";
fillRect(0, 0, can.width, can.height);
fillStyle = "#000000";
fillRect(0, 0, can.width, can.height / 15);
fillStyle = "#ffffff";
font = can.height / 15 + "px Verdana";
fillText("Score: " + score + "/" + mScore, 1, can.height / 19);
fillText("Time: " + time, can.width / 1.5 + 6, can.height / 19);
}
}
function drawLevel() {
map.plates.forEach(e => {
ctx.fillStyle = "#ffffff";
ctx.fillRect(e[0], can.height / 15 + e[1], e[2], e[3]);
});
map.moneys.forEach(e => {
ctx.beginPath();
ctx.fillStyle = "#fcba03";
ctx.arc(e[0] + 12.5, e[1] + 12.5, 4, 0, 2 * Math.PI);
ctx.fill();
});
}
function detectGravity() {
map.plates.forEach(e => {
if (playY > e[1] && playX <= e[0] && playX >= e[0] + e[2]) {
} else {
playY += 0;
}
});
}
function drawPlayer() {
const a = map.player;
if (gameOn == 0 || gameOn == 2) {
playX = a[0];
playY = a[1];
velX = 0;
velY = 0;
}
ctx.beginPath();
ctx.fillStyle = "#ff0000";
ctx.arc(playX + 12.5, playY + 12.5, 4, 0, 2 * Math.PI);
ctx.fill();
}
function drawGame() {
if (gameOn == 0) {
can.style.animation = "none";
with (ctx) {
fillStyle = "rgba(0, 0, 0, 0.5)";
fillRect(0, 0, can.width, can.height);
strokeStyle = "#000000";
lineWidth = 5;
fillStyle = "#ffffff";
textAlign = "center";
strokeText("Click to Start", 150, can.height / 4);
fillText("Click to Start", 150, can.height / 4);
}
} else if (gameOn == 2) {
can.style.animation = "0.2s flash infinite";
with (ctx) {
fillStyle = "rgba(0, 0, 0, 0.5)";
fillRect(0, 0, can.width, can.height);
strokeStyle = "#000000";
lineWidth = 5;
fillStyle = "#ff0000";
textAlign = "center";
strokeText("-- Game Over --", 150, can.height / 4);
fillText("-- Game Over --", 150, can.height / 4);
}
} else {
can.style.animation = "none";
}
}
function drawPixels() {
var fw = (can.width / 2) | 0,
fh = (can.height / 2) | 0;
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.msImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled = false;
ctx.drawImage(can, 0, 0, fw, fh);
ctx.drawImage(can, 0, 0, fw, fh, 0, 0, can.width, can.height);
}
init();
* {
box-sizing: border-box;
overflow: hidden;
}
.block {
border: 2px solid black;
}
#keyframes flash {
0%, 100% {
border: 2px solid black;
}
50% {
border: 2px solid red;
}
}
<canvas class="block"></canvas>
Simple step based gravity.
Gravity
Gravity manifests as a change in speed over time (acceleration). It has a direction and magnitude (a vector)
We define the gravity vector going down the canvas
const gravity = {x: 0, y: 1};
Normally we apply gravity over time units of seconds. This is not a handy unit for animation. In this case we can define it as pixels per frame. A frame is 1/60th of a second. Thus the gravity defined above having a magnitude of 1 pixel per tick squared. So in one second an object would be traveling at 60 pixels per tick or 3600 pixels per second.
This is a little too fast for most animations so we can slow it down somewhat
const gravity = {x: 0, y: 0.1};
The object
An object has a position (a coordinate) and a velocity (a vector) having a direction and magnitude.
const object = {
pos: {x: 0, y: 0}, // position
vel: {x, 0, y: 0}, // velocity
}
To simulate gravity on this object we can add a behavior in the form of a function. In this case we can call it update. In the update function we accelerate the object, by adding the gravity vector to the velocity vector (object.vel). Then we update the position by adding the velocity vector object.vel to the position coordinate object.pos
const gravity = {x: 0, y: 0.1};
const object = {
pos: {x: 0, y: 0}, // position
vel: {x, 0, y: 0}, // velocity
update() {
this.vel.x += gravity.x;
this.vel.y += gravity.y;
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
}
}
The world
By its self this object will fall forever so we need to have it interact with the world. We can define a ground line. In its most basic a line at a y position on the canvas.
const ground = ctx.canvas.height; // ground at bottom of canvas.
To interact we need to add to the objects update function. In this we check the object position against the ground. If the position is below the ground, we move the position up away from the ground the same distance it has moved into the ground, and reverse the velocity (bounced).
We can define the springyness of the ground as a fraction of the velocity.
We also need to give the object a size.
Thus we get.
const gravity = {x: 0, y: 0.1};
const ground = ctx.canvas.height; // ground at bottom of canvas.
const bounce = 0.5;
const object = {
pos: {x: 0, y: 0}, // position
vel: {x, 0, y: 0}, // velocity
size: {w: 10, h: 10},
update() {
this.vel.x += gravity.x;
this.vel.y += gravity.y;
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
const g = ground - this.size.h; // adjust for size
if(this.pos.y >= g) {
this.pos.y = g - (this.pos.y - g); //
this.vel.y = -Math.abs(this.vel.y) * bounce; // change velocity to moving away.
}
}
}
Then all that is needed is to call update every frame and draw the object at the correct position.
Demo
Putting it into practice.
A simple box called object falls from the top of the canvas and hits the ground (bottom of the canvas) bounces a bit and stop. (Click to reset)
Update: I forgot to check if the object is at rest.
The math will have the box vibrate and never really stop moving if we don't add a little extra code to update.
The box will now appear to come to a complete stop when its bounce is less than gravity. See comment // check for rest.
const ctx = canvas.getContext("2d");
canvas.width = innerWidth-4;
canvas.height = innerHeight-4;
requestAnimationFrame(mainLoop); // starts the animation
const gravity = {x: 0, y: 0.1};
const ground = ctx.canvas.height; // ground at bottom of canvas.
const bounce = 0.9; // very bouncy
const object = {
pos: {x: ctx.canvas.width / 2, y: 0}, // position halfway on canvas
vel: {x: 0, y: 0}, // velocity
size: {w: 10, h: 10},
update() {
this.vel.x += gravity.x;
this.vel.y += gravity.y;
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
const g = ground - this.size.h; // adjust for size
if(this.pos.y >= g) {
this.pos.y = g - (this.pos.y - g); //
this.vel.y = -Math.abs(this.vel.y) * bounce;
if (this.vel.y >= -gravity.y) { // check for rest.
this.vel.y = 0;
this.pos.y = g - gravity.y;
}
}
},
draw() { ctx.fillRect(this.pos.x, this.pos.y, this.size.w, this.size.h) },
reset() { this.pos.y = this.vel.y = this.vel.x = 0 },
}
function mainLoop() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
object.update(); // move object
object.draw();
requestAnimationFrame(mainLoop);
}
canvas.addEventListener("click", object.reset.bind(object));
body {
margin: 0px;
padding: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
border: 1px solid black;
}
<canvas id="canvas"></canvas>
Problem: Im drawing a spaceship on the canvas. Upon hovering over it's x/y, im drawing an arc on the canvas, indicating the starships weapons angle and range (considering the starships current Baring/facing). Currently the determined angle is being drawn in green and extends as far as the weapons range value allows.
However, i would like to use a gradiant to fill the determined arc to indicate a drop-off in accuracy (i.e. gradiant begins at green, moves to orange, turns red the further away from the starships Position the angle is).
However, i dont know how i could replace my stock ctx.fill() on the drawn arc with a gradiant.
var ship {
loc: {x, y}, // lets say 100, 100
facing: facing // lets say facing 0, i.e. straight right
weapons: objects (range, startArc, endArc) // lets say 50, 300, 60 -> 120 degree angle, so -60 and +60 from facing (0/360)
}
for (var i = 0; i < weapon.arc.length; i++){
var p1 = getPointInDirection(weapon.range, weapon.arc[i][0] + angle, pos.x, pos.y);
var p2 = getPointInDirection(weapon.range, weapon.arc[i][1] + angle, pos.x, pos.y)
var dist = getDistance( {x: pos.x, y: pos.y}, p1);
var rad1 = degreeToRadian(weapon.arc[i][0] + angle);
var rad2 = degreeToRadian(weapon.arc[i][1] + angle);
fxCtx.beginPath();
fxCtx.moveTo(pos.x, pos.y);
fxCtx.lineTo(p1.x, p1.y);
fxCtx.arc(pos.x, pos.y, dist, rad1, rad2, false);
fxCtx.closePath();
fxCtx.globalAlpha = 0.3;
fxCtx.fillStyle = "green";
fxCtx.fill();
fxCtx.globalAlpha = 1;
}
is it possible to replace the arc/globalalpha/fill so use a gradiant flow instead of it being colored fixed and if so, how ?
thanks
To fill an arc with a gradient, animated just for the fun.
Uses a radial gradient and set colour stops as a fraction of distance.
The function createRadialGradient takes 6 numbers the position x,y and start radius and the position x,y and end radius of the gradient.
Colour stops are added via the gradient object addColorStop function that takes a value 0 inner to 1 outer part of the gradient and the colour as a CSS color string. "#F00" or "rgba(200,0,0,0.5)" or "RED"
Then just use the gradient as the fill style.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function update(time) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// position of zones in fractions
var posRed = 0.8 + Math.sin(time / 100) * 0.091;
var posOrange = 0.5 + Math.sin(time / 200) * 0.2;
var posGreen = 0.1 + Math.sin(time / 300) * 0.1;
var pos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var dist = 100;
var ang1 = 2 + Math.sin(time / 1000) * 0.5;
var ang2 = 4 + Math.sin(time / 1300) * 0.5;
var grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, dist);
grad.addColorStop(0, "#0A0");
grad.addColorStop(posGreen, "#0A0");
grad.addColorStop(posOrange, "#F80");
grad.addColorStop(posRed, "#F00");
grad.addColorStop(1, "#000");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
ctx.arc(pos.x, pos.y, dist, ang1, ang2);
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
Do you someone might know how to make this kind of animation.
How I managed to see more word about a canvas javascript technique.
http://cuberto.com/
Thank you
Here's a single arc on a blue background going around and around. It is achieved by drawing a white arc on a blue background.
Fiddle:
https://jsfiddle.net/07d69v39/1/
//coordinates of rectangle
var xp = 125;
var yp = 125;
var radius = 45;
//How far to move the arc each time:
var angleStep = Math.PI * 0.1;
//How often to move the arc:
var stepTime = 50;
var currentStep = 0;
function doDraw() {
var can = document.getElementById("myCanvas");
can.width = 250;
can.height = 250;
var context = can.getContext("2d");
//Erase the canvas by painting it blue:
context.fillStyle="#0000FF";
context.fillRect(0, 0, 250, 250);
//Set the drawing color to white:
context.strokeStyle="#FFFFFF";
context.lineWidth=5;
context.arc(xp, yp, radius, 0 + currentStep, 1.5*Math.PI + currentStep);
context.stroke();
//Make sure to maintain the currentStep angle so it doesn't overflow:
currentStep = currentStep + angleStep;
if (currentStep>Math.PI * 2) {
currentStep = currentStep - Math.PI * 2;
}
//Wait, and then call this function again to animate:
setTimeout(function() {
doDraw();
}, stepTime);
}
doDraw();
To complete the effect, you will need multiple concentric arcs, traveling in opposite directions:
I placed values for individual arc behavior in the circles[] array:
https://jsfiddle.net/07d69v39/3/
//coordinates of rectangle
var xp = 125;
var yp = 125;
var circles = [
{
radius: 45,
step: 0,
direction: true,
speed: Math.PI * 0.1,
},
{
radius: 42,
step: 0,
direction: false,
speed: Math.PI * 0.05
},
{
radius: 39,
step: 0,
direction: true,
speed: Math.PI * 0.07
},
{
radius: 36,
step: 0,
direction: false,
speed: Math.PI * 0.02
},
{
radius: 33,
step: 0,
direction: true,
speed: Math.PI * 0.06
},
{
radius: 30,
step: 0,
direction: false,
speed: Math.PI * 0.04
}
];
//How often to move the arc:
var stepTime = 50;
function doDraw() {
var can = document.getElementById("myCanvas");
can.width = 250;
can.height = 250;
var context = can.getContext("2d");
context.imageSmoothingEnabled= true;
//Erase the canvas by painting it blue:
context.fillStyle="#0000FF";
context.fillRect(0, 0, 250, 250);
//Set the drawing color to white:
context.strokeStyle="#FFFFFF";
context.lineWidth = 4;
for (var i = 0; i<circles.length;i++) {
var arc = circles[i];
maintainArc(context, arc);
}
//Wait, and then call this function again to animate:
setTimeout(function() {
doDraw();
}, stepTime);
}
function maintainArc(context, arc) {
var radius = arc.radius;
var step = arc.step;
context.beginPath();
context.arc(xp, yp, radius, 0 + step, 1.5*Math.PI + step);
context.stroke();
//maintain the step angle differently for clockwise and counter clockwise
if (arc.direction) {
step = step + arc.speed;
if (step>Math.PI * 2) {
step = step - Math.PI * 2;
}
} else {
step = step - arc.speed;
if (step<Math.PI * 2) {
step = step + Math.PI * 2;
}
}
arc.step = step;
}
doDraw();
What's missing now is some artistic flare to make the revolving circles align into a 'C' at the proper moment. I also see that the 'C' in the example you provided fades out as the page load completes. This does not do that.
I have this awesome piece of code.
The idea, as you can imagine,is to draw a grid of rectangles. I want a big grid, let's say 100 X 100 or more.
However, when i run the awesome piece of code for the desired size (100X 100), my browser crashes.
How can i achieve that?
* please note: when i say 100X100 i mean the final number of rectangles (10k) not the size of the canvas.
thank u
function init() {
var cnv = get('cnv');
var ctx = cnv.getContext('2d');
var ancho = 12; // ancho means width
var alto = 12; // alto means height
ctx.fillStyle = randomRGB();
for (var i = 0; i < cnv.width; i+= ancho) {
for (var j = 0; j < cnv.height; j+= alto) {
//dibujar means to draw, rectangulo means rectangle
dibujarRectangulo(i+ 1, j+1, ancho, alto, ctx);
}
}
}
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.rect(x, y, ancho, alto);
ctx.fill();
ctx.closePath();
}
The dibujarRectanglo() function calls rect() function which adds a closed rectanglar subpath to the current path. Then calls fill() function to fill the current path. Then calls closePath() function to close the subpath, which does nothing since the subpath is already closed.
In other words, the first dibujarRectanglo() function call is painting a path that contains 1 rectangle subpath. The second call is painting a path that contains 2 rectangle subpaths. The third call is painting a path that contains 3 rectangle subpaths. And so on. If the loop calls dibujarRectanglo() function 10000 times then a total of 1+2+3+...+10000 = 50005000 (i.e. over 50 million) rectangle subpaths will be painted.
The dibujarRectangle() function should be starting a new path each time. For example...
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.beginPath();
ctx.rect(x, y, ancho, alto);
ctx.fill();
}
Then 10000 calls will only paint 10000 rectangle subpaths which is a lot faster that painting 50 million rectangle subpaths.
16,384 boxes on the wall
As I said in the comment its easy to draw a lot of boxes, it is not easy to have them all behave uniquely. Anyways using render to self to duplicate boxes exponential there are 128 * 128 boxes so that's 16K, one more iteration and it would be 64K boxes.
Its a cheat, I could have just drawn random pixels and called each pixel a box.
Using canvas you will get upto 4000 sprites per frame on a top end machine using FireFox with each sprite having a location, center point, rotation, x and y scale, and an alpha value. But that is the machine going flat out.
Using WebGL you can get much higher but the code complexity goes up.
I use a general rule of thumb, if a canva 2D project has more than 1000 sprites then it is in need of redesign.
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
/** CreateImage.js begin **/
var createImage = function (w, h) {
var image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
/** CreateImage.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var boxSize = 10;
var boxSizeH = 5;
var timeDiv = 1.2;
var bBSize = boxSize * 128; // back buffer ssize
var buff = createImage(bBSize, bBSize);
var rec = createImage(boxSize, boxSize);
var drawRec = function (ctx, time) {
var size, x, y;
size = (Math.sin(time / 200) + 1) * boxSizeH;
ctx.fillStyle = "hsl(" + Math.floor((Math.sin(time / 500) + 1) * 180) + ",100%,50%)";
ctx.strokeStyle = "Black";
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, boxSize, boxSize);
x = Math.cos(time / 400);
y = Math.sin(time / 400);
ctx.setTransform(x, y, -y, x, boxSizeH, boxSizeH)
ctx.fillRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
ctx.strokeRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
}
function update(time) {
var fw, fh, px, py, i;
time /= 7;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.clearRect(0, 0, bBSize, bBSize)
buff.ctx.drawImage(rec, 0, 0);
buff.ctx.drawImage(rec, boxSize, 0);
fw = boxSize + boxSize; // curent copy area width
fh = boxSize; // curent copy area height
px = 0; // current copy to x pos
py = boxSize; // current copy to y pos
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make square
for (i = 0; i < 6; i++) {
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fh += fh; // double size across
px = fw;
py = 0;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make rec
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fw += fw; // double size down
px = 0;
py = fh;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh);
}
// draw the boxes onto the canvas,
ctx.drawImage(buff, 0, 0, 1024, 1024);
requestAnimationFrame(update);
}
update();
.canv {
width:1024px;
height:1024px;
}
<canvas id="can" class = "canv" width=1024 height=1024></canvas>
The goal is to have fireworks come up over top of an existing web page, so that you can see both the existing page, and the fireworks exploding over top of it. I successfully got them over top of the page, however, now they do not fade out. I'm left with white build up over top of web page.
I have this jsfiddle:
http://jsfiddle.net/2EQ2w/1/
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
document.body.insertBefore(canvas, document.body.firstChild);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(mousePos.x);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.001)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, 0)");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ,255)");
gradient.addColorStop(0.1, "rgba(0, 0, 0, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Which was built off this base:
http://jsfiddle.net/dtrooper/AceJJ/
Does anyone know how I can get these fireworks to fade out? Or get the particle to fade out after it hasn't moved for a few milliseconds?
You can definitely have this with fading trails:
http://jsfiddle.net/LgjG8/
Just set up a second off-screen canvas that has a reduced global alpha:
// create 2nd canvas
var canvas2 = document.createElement('canvas'),
context2 = canvas2.getContext('2d');
canvas2.width = canvas.width;
canvas2.height = canvas.height;
// reduce alpha of second canvas
context2.globalAlpha = 0.8;
Then instead of simply wiping the canvas clean each frame, copy the first on-screen canvas to the second. This will produce a faded copy of the visible canvas due to the lowered global alpha value. Then wipe the first canvas before copying the faded version back. Finally, just update the canvas as normal. This will produce a trail.
// produce faded copy of current canvas
context2.clearRect(0, 0, canvas2.width, canvas2.height);
context2.drawImage(canvas, 0, 0);
// redraw faded copy on original canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(canvas2, 0, 0);
I didn't really look through your code so you might need to play with this a little, but you get the idea.
The fireworks use fillRect() with a low opacity to clear (fade out) old fireworks. As a result, nothing behind the canvas will show.
However, you can use clearRect() instead so that the canvas does not have a solid background. The problem with this is that the fireworks don't leave nice trails because there is no low opacity fill to fade them out.
Not optimal, but at least the fireworks are in front of the other page content. I wish there was a clearStyle you could set to low opacity but, sadly, no.
// clear canvas
//context.fillStyle = "rgba(0, 0, 0, 0.05)";
//context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
WORKING EXAMPLE
Since you want to have the content visible, you can try to change the way the trails are generated and use the clearRect. Instead to have the trails done by the c.fill() you can make it to be done by particles, so you can view them.
In the Rocket.prototype.render you can do this:
//c.fill();
var particle = new Particle(this.pos);
particle.shrink = Math.random() * 0.05 + 0.93;
particle.size = 10;
particles.push(particle);
And the trails will be visible then.
Example
Before edited answer (not working as asker expected):
In the loop() function you have a really small alpha, making that the fireworks are not fading out.
Try to change:
context.fillStyle = "rgba(0, 0, 0, 0.001)";
to
context.fillStyle = "rgba(0, 0, 0, 0.05)";
Hope it helps!