Canvas rotating particles - javascript

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.

Related

How do I slow an animation down in canvas-sketch?

In line 6, you'll see that I have animate as true. I tried adding a duration and lowering the fps, but this doesn't impact the movement speed. I've come to the conclusion that the duration and fps don't impact the animation speed as it doesn't impact other canvas sketch animations I've worked on. I could be wrong.
Thanks for checking out this post.
const canvasSketch = require('canvas-sketch');
const math = require('canvas-sketch-util/math');
const random = require('canvas-sketch-util/random');
const settings = {
dimensions: [ 1080, 1080 ],
animate: true, // <––––––––––––––––––––– line 6
duration: 3, // <–––––––––––––––––––––
fps: 5, // <–––––––––––––––––––––
};
const sketch = () => {
return ({ context, width, height }) => {
context.fillStyle = 'white';
context.fillRect(0, 0, width, height);
// Gradient
let gradient = context.createLinearGradient(0, 0, width * 1.2, 0);
gradient.addColorStop("0", "gold");
gradient.addColorStop("0.03" ,"mediumblue");
gradient.addColorStop("0.99" ,"gold");
context.fillStyle = gradient;
context.strokeStyle = gradient;
const cx = width * 0.5;
const cy = height * 0.5;
const w = width * 0.012;
const h = height * 0.12;
let x, y;
const num = 60;
const radius = width * 0.36
for (let i = 0; i < num; i++) {
const slice = math.degToRad(360/ num);
const angle = slice * i;
x = cx + radius * Math.sin(angle);
y = cy + radius * Math.cos(angle);
// Scale of strokes
context.save();
context.translate(x, y);
context.rotate(-angle);
context.scale(random.range(0.1, 2.1), random.range(0.12, 1.2));
// Placement of strokes
context.beginPath();
context.rect(-w * 2.7, random.range(3, -h * 1.5), w, h);
context.fill();
context.restore();
context.save();
context.translate(cx, cy);
context.rotate(-angle);
// Arc thickness
context.lineWidth = random.range(5, 20);
// Arc/slice width
context.beginPath();
// Arc distance from the center, arc rotation length
context.arc(0, 0, radius * random.range(0.6, 1.2), slice * random.range(0.6, -33), slice * random.range(1.2,45));
context.stroke();
context.restore();
}
};
};
canvasSketch(sketch, settings);

How do I emulate Gravity in a canvas with a Rectangle or Arc?

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>

Canvas get pixelate after adding animation

After adding the animation, the canvas gets pixelated. I've tried to fix this with adding context.clearRect(0, 0, canvas.width, canvas.height); but it hides the previous segments
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = x - 40;
var endPercent = 45;
var curPerc = 0,
mybeg;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
var col = ['#000', '#ff0000', '#002bff'];
function animate(current, colr, mybeg) {
context.beginPath();
context.moveTo(x, y);
context.arc(x, y, radius, mybeg, ((circ) * current));
context.fillStyle = colr;
context.fill();
//console.log(x, y, radius, mybeg, ((circ) * current));
curPerc++;
if (curPerc <= endPercent) {
mybeg = 0;
requestAnimationFrame(function() {
animate(curPerc / 100, col[0], mybeg)
});
} else if (curPerc > 44 && curPerc <= 65) {
const mybeg1 = ((circ) * 45 / 100);
requestAnimationFrame(function() {
animate(curPerc / 100, col[1], mybeg1)
});
} else if (curPerc > 64 && curPerc <= 100) {
const mybeg2 = ((circ) * 65 / 100);
requestAnimationFrame(function() {
animate(curPerc / 100, col[2], mybeg2)
});
}
}
animate();
<canvas id="myCanvas" height="300" width="300"></canvas>
You are redrawing the same arc over itself a lot of times.
To render a smooth arc, we need semi-transparent pixels (antialiasing), and drawing semi-transparent pixels over other semi-transparent pixels will make them more an more opaque.
So the solution here is to clear everything and redraw everything at every frame.
There are several ways to do it, but one of the simplest might be to render your complete pie every-time and only animate a mask over it, using compositing:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = x - 40;
var stops = [
//[ begin, end , color ]
[ 0, 45, '#000' ],
[ 45, 65, '#ff0000' ],
[ 65, 100, '#002bff' ]
];
var current = 0;
animate();
function drawFullPie() {
stops.forEach( function( stop , i) {
var begin = (stop[0] / 100 ) * Math.PI * 2;
var end = (stop[1] / 100 ) * Math.PI * 2;
context.beginPath();
context.moveTo( x, y );
context.arc( x, y, radius, begin, end );
context.fillStyle = stop[2];
context.fill();
} );
}
function drawMask() {
var begin = 0;
var end = (current / 100) * Math.PI * 2;
// Keep whatever is currently painted to the canvas
// only where our next drawings will appear
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.moveTo( x, y );
context.arc( x, y, radius, begin, end );
context.fill();
// disable masking
context.globalCompositeOperation = 'source-over';
}
function animate() {
// clear at every frame
context.clearRect( 0, 0, canvas.width, canvas.height );
// draw the full pie
drawFullPie();
// mask it as needed
drawMask();
// until complete
if( current ++ < 100 ) {
// do it again
requestAnimationFrame( animate );
}
}
<canvas id="myCanvas" height="300" width="300"></canvas>

HTML 5 Canvas Over-layed on top of a Web Page?

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!

Drawing multiple particle elements on an html5 canvas without render kill

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.

Categories

Resources