How to undraw the a rectangle when recalling a function [duplicate] - javascript

This question already has answers here:
How to clear the canvas for redrawing
(25 answers)
Closed 3 years ago.
I want to make a rectangle that moves from the right to the left.
I could draw a new rectangle in the left of the previous one but i couldn't erase the previous one.
Here is my code:
let cvs = document.getElementById("canvas");
let ctx = cvs.getContext('2d');
let firstpos = 400;
let blocks = [];
blocks[0] = {
x: 400,
y: Math.floor(Math.random() * 360)
}
function draw() {
for (var i = 0; i < blocks.length; i++) {
ctx.beginPath()
ctx.fillStyle = "white";
ctx.fillRect(blocks[i].x, 0, 70, blocks[i].y);
ctx.fill();
blocks[i].x -= 0;
}
requestAnimationFrame(draw);
}
draw()
#canvas {
background-color: #000;
}
<canvas id="canvas" width="1000" height="500"></canvas>

Working snippet
Take advantage of CanvasRenderingContext2D.clearRect()
let cvs = document.getElementById("canvas");
let ctx = cvs.getContext('2d');
let firstpos = 400;
let blocks = [];
blocks[0] = {
x: 0,
y: Math.floor(Math.random() * 360)
}
function draw() {
blocks[0].x++;
ctx.clearRect(0, 0, canvas.width, canvas.height); // canvas clear up
for (var i = 0; i < blocks.length; i++) {
ctx.beginPath()
ctx.fillStyle = "white";
ctx.fillRect(blocks[i].x, 0, 70, blocks[i].y);
ctx.fill();
blocks[i].x -= 0;
}
requestAnimationFrame(draw);
}
setInterval(draw, 500)
#canvas {
background-color: #000;
}
<canvas id="canvas" width="1000" height="500"></canvas>

It's not possible to "undraw" something in canvas. However, you can clear the canvas on each frame with clearRect and re-draw everything in a new position.
Additionally, the current code uses blocks[i].x -= 0; which won't change the animation state even with a clear and redraw.
Parameters to fillRect appear incorrect or mislabeled. ctx.fillRect(blocks[i].x, 0, 70, blocks[i].y); should be ctx.fillRect(blocks[i].x, blocks[i].y, width, height);. There's also no need for creating a path or calling fill for this method.
It's typical to encapsulate all data for a block inside the object. We need a color, speed and x/y/height/width.
Note that y: Math.floor(Math.random() * 360) can result in a height of zero.
Here's a simple example of moving a block on the canvas:
const cvs = document.getElementById("canvas");
cvs.width = innerWidth;
cvs.height = innerHeight;
const ctx = cvs.getContext("2d");
const blocks = [{
x: innerWidth - 50,
y: 20,
velocityX: -1,
width: 50,
height: 50,
color: "white"
}];
(function draw() {
ctx.clearRect(0, 0, cvs.width, cvs.height);
for (const block of blocks) {
ctx.fillStyle = block.color;
ctx.fillRect(block.x, block.y, block.width, block.height);
block.x += block.velocityX;
}
requestAnimationFrame(draw);
})();
#canvas {
background-color: #000;
}
<canvas id="canvas"></canvas>

Related

How to fully fade out contents in canvas

This question has been asked twice without the caveat of "Fully Fade Out"
Fastest way of fading out entire contents of a canvas to transparency, not other color
HTML5: Apply transparency to Canvas after drawing through JavaScript
Both of the accepted answers only partially fade out the contents. They both suggest something like:
// semi functional code, but doesn't fully work
ctx.save();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "destination-in";
ctx.fillStyle = "rgba(0, 0, 0, 0.9)";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
ctx.restore();
This leaves residue everywhere, never fully fading out anything. See example below:
let canvas = document.getElementsByTagName('canvas')[0];
let ctx = canvas.getContext('2d');
let rand = n => Math.floor(Math.random() * n);
setInterval(() => {
ctx.beginPath();
ctx.arc(rand(300), rand(120), rand(60), Math.PI * 2, 0);
ctx.fillStyle = `rgba(${rand(256)}, ${rand(256)}, ${rand(256)}, 1)`;
ctx.globalCompositeOperation = 'source-over';
ctx.fill();
}, 1000);
let fadeOut = () => {
let fadeAmount = 0.05;
// Note that the colour here doesn't matter! Only the alpha matters.
// The colour here is red, but you'll see no red appear
ctx.fillStyle = `rgba(255, 0, 0, ${1 - fadeAmount})`;
ctx.globalCompositeOperation = 'destination-in';
ctx.fillRect(0, 0, 300, 120);
requestAnimationFrame(fadeOut);
};
requestAnimationFrame(fadeOut);
canvas { border: 3px solid #808080; background-color: #000000; }
<canvas width="300" height="120"></canvas>
The question is: How can one fully fade out elements on a canvas, all the way to transparent?
EDIT: I'm searching for a performant solution that works for heavily layered (think visualizer) situations.
Here is a small sample using globalAlpha, looks good to me, no residue...
each FadingCircle will have own fade, that will determine how fast it fades and if it goes to 0 or below we do not draw it, seems like an easy solution.
You can add colors, random positions and change it as much as you like to suit your needs.
const canvas = document.getElementsByTagName('canvas')[0];
const ctx = canvas.getContext('2d');
class FadingCircle {
constructor(x, y, radius, fade) {
this.x = x
this.y = y
this.radius = radius
this.fade = fade
this.globalAlpha = 1
}
draw(ctx) {
if (this.globalAlpha > 0) {
ctx.beginPath()
ctx.globalAlpha = this.globalAlpha
ctx.arc(this.x, this.y, this.radius, Math.PI * 2, 0)
ctx.fill()
this.globalAlpha -= this.fade
}
}
}
let sml = new FadingCircle(40, 50, 20, 0.01)
let med = new FadingCircle(140, 50, 30, 0)
let big = new FadingCircle(100, 50, 50, 0.005)
let animation = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
big.draw(ctx)
med.draw(ctx)
sml.draw(ctx)
requestAnimationFrame(animation);
};
requestAnimationFrame(animation);
<canvas width="300" height="120"></canvas>

set grid made by js on the background of canvas

I try to make paint app in javascript. I need to make square grid and by pushing button. I made such grid but it is not on the background. How should I pass grid made by js on the background?
function print_grid()
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillRect(0, 0, 5100, 5100);
ctx.clearRect(0, 0, 5100, 5100);
ctx.beginPath();
for (let i = 0; i < 39; i++)
{
ctx.lineWidth = 1;
ctx.moveTo(50*i, 0);
ctx.lineTo(50*i, 5100);
ctx.moveTo(0, 50*i);
ctx.lineTo(5100, 50*i);
}
ctx.stroke();
}
<!DOCTYPE html>
<html>
<body>
<h1>Board</h1>
<button onclick="print_grid()">square</button>
<p >draw!!!</p>
<canvas id="myCanvas" width="1000" height="1000" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
</body>
</html>
One easy solution is using two canvases layered on top of each other. This way you can draw the grid onto the background layer independent from the canvas in the foreground.
Here's an example:
const canvas = document.getElementById('canvas2');
const ctx = canvas.getContext('2d');
let coordinates = {
x: 0,
y: 0
};
let painting = false;
function getPosition(event) {
coordinates.x = event.clientX - canvas.offsetLeft;
coordinates.y = event.clientY - canvas.offsetTop;
}
function startPainting(event) {
painting = true;
getPosition(event);
}
function stopPainting() {
painting = false;
}
function draw(event) {
if (!painting) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = 'red';
ctx.moveTo(coordinates.x, coordinates.y);
getPosition(event);
ctx.lineTo(coordinates.x, coordinates.y);
ctx.stroke();
}
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function printGrid() {
let backgroundCanvas = document.getElementById('canvas');
let context = backgroundCanvas.getContext("2d");
context.beginPath();
for (let a = 0; a < 10; a++) {
context.moveTo(0, parseInt(a * (backgroundCanvas.height / 9)));
context.lineTo(backgroundCanvas.width, parseInt(a * (backgroundCanvas.height / 9)));
context.moveTo(parseInt(a * (backgroundCanvas.width / 9)), 0);
context.lineTo(parseInt(a * (backgroundCanvas.width / 9)), backgroundCanvas.height);
}
context.stroke();
context.closePath();
}
printGrid();
document.addEventListener('mousedown', startPainting);
document.addEventListener('mouseup', stopPainting);
document.addEventListener('mousemove', draw);
<button onclick='clearCanvas();'>Clear</button>
<div>
<canvas id='canvas' style='position: absolute'></canvas>
<canvas id='canvas2' style='position: absolute'></canvas>
</div>

How to use clearRect to not draw an moving object on canvas

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();
}
}

javascript my arc is not showing up or moving across the screen

hello i am trying to make a white circle move across a black box in javascript my circle is not showing up the big black box does but the circle does not show up i dont know why i am loading the page in google chrome here is the code
<html>
<head>
<title>paddle</title>
</head>
<body>
<canvas id="myCanvas" width="800" height="600"></canvas>
<script>
var canvas
var canvasContext
var ballX = 5
window.onload = function() {
var fps = 30;
setInterval(updateAll, 1000)
canvas = document.getElementById("myCanvas");
canvasContext = canvas.getContext("2d")
canvasContext.fillStyle = "black"
canvasContext.fillRect(0, 0, canvas.width, canvas.height)
}
function updateAll() {
ballX++
canvasContext.fillStyle = "white";
canvasContext.beginPath()
canvasContext.arc(ballX, 100, 10, 0, Math.PI*2, true);
canvasContext.stroke()
}
</script>
</body>
</html>
The problem is that you are using stroke to draw the circle but you have not set the stroke style which is by default black. So you are drawing a black circle on a black background. Hence no see circle.
Also it is best to use requestAnimationFrame to animate rather than set interval.
Example animating a circle
requestAnimationFrame(animationLoop);
const ctx = myCanvas.getContext("2d");
var ballX = 5;
var speed = 1
const radius = 10;
const fps = 30;
var frameCount = 0;
function animationLoop() {
if (frameCount % (60 / fps) === 0) {
ctx.fillStyle = "black"
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
draw();
}
frameCount ++;
requestAnimationFrame(animationLoop);
}
function draw() {
ballX = (ballX + speed) % (ctx.canvas.width + radius * 2);
ctx.strokeStyle = "white";
ctx.lineWidth = 2;
ctx.beginPath()
ctx.arc(ballX - radius, 20, 10, 0, Math.PI * 2);
ctx.stroke()
}
<canvas id="myCanvas" width="600" height="40"></canvas>

HTM5 Canvas, suggestions for changing animation effect

So I'm new to canvas, and am still figuring out how things work. I am trying to make an animation of a volcano eruption. I separated the volcano and sky onto one layer, the eruption on the second layer, and the ash cloud on the third. I was referencing an example for the eruption animation, and in the way it is written, it blacks out the canvas. Is there a different way of achieving the same effect it already makes, but so that the opacity of the layer is all the way down so you can see the volcano and sky underneath the eruption? Here's my code:
<!doctype html>
<html lang='en'>
<head>
<style>
body {
font-family: Verdana, Helvetica, sans-serif;
}
canvas {
border: 1px solid black;;
}
</style>
</head>
<body>
<div id="canvasesdiv" style="position:relative; width:400px; height:300px">
<canvas id="layer1" style="z-index: 1; position:absolute; left:0px; top:0px;" width="800" height="500"></canvas>
<canvas id="layer2" style="z-index: 2; position:absolute; left:0px; top:0px;" width="800" height="500"></canvas>
<canvas id="layer3" style="z-index: 3; position:absolute; left:0px; top:0px;" width="800" height="500"></canvas>
</div>
<script>
//var canvas = document.getElementById("myCanvas");
//var context = canvas.getContext("2d");
var layer1;
var layer2;
var layer3;
var particles;
var eruption;
var timer;
var timerRestart;
function init(){
layer1 = document.getElementById("layer1");
ctx1 = layer1.getContext("2d");
layer2 = document.getElementById("layer2");
ctx2 = layer2.getContext("2d");
canvas=layer3 = document.getElementById("layer3");
context=ctx3 = layer3.getContext("2d");
}
function animationHandler(){
fillBackgroundColor(canvas, context);
drawVolcano();
drawClouds();
eruption = setTimeout(makeParticles, 10);
}
function drawClouds(){
ctx3.beginPath();
ctx3.moveTo(0, 100);
ctx3.bezierCurveTo(0, 100, 75, 200, 150, 100);
ctx3.bezierCurveTo(150, 100, 225, 200, 300, 85);
ctx3.bezierCurveTo(300, 85, 375, 200, 450, 75);
ctx3.bezierCurveTo(450, 75, 525, 200, 600, 100);
ctx3.bezierCurveTo(600, 100, 700, 200, 800, 100);
ctx3.lineTo(800, 0);
ctx3.lineTo(0, 0);
ctx3.closePath();
ctx3.fillStyle = "#6f2a2a";
ctx3.fill();
ctx3.lineWidth = 5;
ctx3.strokeStyle = "#371515";
ctx3.stroke();
}
function drawVolcano(){
ctx1.beginPath();
ctx1.moveTo(0, 400);
ctx1.bezierCurveTo(0, 400, 250, 400, 325, 200);
ctx1.lineTo(425, 200);
ctx1.bezierCurveTo(425, 200, 450, 400, 800, 400);
ctx1.lineTo(800, 500);
ctx1.lineTo(0, 500);
ctx1.closePath();
ctx1.fillStyle = "#802b00";
ctx1.fill();
ctx1.lineWidth = 5;
ctx1.strokeStyle = "#b33c00";
ctx1.stroke();
}
function fillBackgroundColor(canvas, context){
ctx1.fillStyle = "#3399ff" ;
ctx1.fillRect(0, 0, canvas.width, canvas.height);
}
function makeParticles() {
//create an array of particles for our animation
particles = [];
for(var i = 0; i < 100; i++)
{
particles.push(new Particle());
}
}
function degreesToRadians(degrees) {
//converts from degrees to radians and returns
return (degrees * Math.PI)/180;
}
function Particle(){
//the constructor for a single particle, with random starting x+y, velocity, color, and radius
//this.x = Math.random()*canvas.width;
//this.y = Math.random()*canvas.height;
this.x = canvas.width/2;
this.y = (0,0);
this.vx = Math.random()*16-8;
this.vy = Math.random()*10;
var colors = ["red", "#ff6600", "yellow", "#262626"];
this.color = colors[Math.floor(Math.random()*colors.length)];
this.radius = 50;
}
function moveParticles() {
//partially clear the screen to fade previous circles, and draw a new particle at each new coordinate
ctx2.globalCompositeOperation = "source-over";
ctx2.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx2.fillRect(0, 0, canvas.width, canvas.height);
ctx2.globalCompositeOperation = "lighter";
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
ctx2.beginPath();
ctx2.arc(p.x, p.y, p.radius, 0, degreesToRadians(360), true);
ctx2.fillStyle = p.color;
ctx2.fill();
p.x += p.vx;
p.y += p.vy;
if(p.x < -50) p.x = canvas.width+50;
if(p.y < -50) p.y = canvas.height+50;
if(p.x > canvas.width+50) p.x = -50;
if(p.y > canvas.height+50) p.y = -50;
p.radius -= 1;
}
}
function clearScreen(color) {
//clears the screen and fills with the color of choice
ctx2.clearRect(0, 0, canvas.width, canvas.height);
ctx2.fillStyle = color;
ctx2.fillRect(0, 0, canvas.width, canvas.height);
}
window.onload = function() {
init();
animationHandler();
timer = setInterval(moveParticles, 60);
//timerRestart = setInterval(makeParticles, 4000);
}
</script>
</html>
This is where the animation happens:
function moveParticles() {
//partially clear the screen to fade previous circles, and draw a new particle at each new coordinate
ctx2.globalCompositeOperation = "source-over";
ctx2.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx2.fillRect(0, 0, canvas.width, canvas.height);
ctx2.globalCompositeOperation = "lighter";
It looks like the previously drawn particles are "dimmed" by overwriting the entire canvas with a low-alpha fill. But, this also undesirably causes the underlying volcano to be "dimmed".
Instead of repeatedly "dimming" by filling the whole canvas with rgba(0,0,0,0.3), you could reduce each individual particle's alpha with each new frame.
This can be done at the particle level by changing each particle's rgba fill.
Example:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var particle={
// start with red base color
// use a token (here #) which will be replaced with alpha
baseColor:'rgba(255,0,0,#)',
// start with the particle at full alpha
// this alpha will be incrementally reduced
currentAlpha:1.00,
};
requestAnimationFrame(animate);
function animate(time){
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.arc(150,50,20,0,Math.PI*2);
// change this particle's alpha
var fill=particle.baseColor.replace('#',particle.currentAlpha);
particle.currentAlpha-=.01;
if(particle.currentAlpha<=0){particle.currentAlpha=0;}
ctx.fillStyle=fill;
ctx.fill();
requestAnimationFrame(animate);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>A particle with reducing rgba alpha</h4>
<canvas id="canvas" width=300 height=300></canvas>
For better performance, you could draw all particles with the same alpha value in a batch. This method would use context.globalAlpha to draw a batch of particles rather than changing each particle's rgba.

Categories