ANDIOS
ANDIOS

Reputation: 43

Draw multiples falling circles on HTML5 canvas using JavaScript

//read where the click is made
$(document).click(function(e) { 
    var x = event.clientX;
    var y = event.clientY;
    drawCircle(x,y);
});


//array used for random colors of circles
var color = ["#E72B8E", "#238A9B", "#F05C3F"];  

//setting canvas and its size
var c = document.getElementById("canvas");

c.width  = window.innerWidth;
c.height = window.innerHeight;

var ctx = c.getContext("2d");

//function to draw circles
function drawCircle(x,y){

    //randomly chose color and size for circles
    var randC = color[Math.floor(Math.random() * color.length)];
    var randS = Math.floor(Math.random() * (80 - 30)) + 30;

    //draw circles
    ctx.beginPath();
    ctx.arc(x,y,randS,0,2*Math.PI);

    //make gradient for random color and fill
    var grd=ctx.createLinearGradient(0,0,170,0);
    grd.addColorStop(0,randC);
    grd.addColorStop(1,"transparent");

    ctx.fillStyle=grd;
    ctx.fill();
    ctx.closePath();
}

So, what I'm trying to do is to draw a circle every time I click on the canvas and make it fall down.

My first problem is that I'm stuck on how to make my circles remained on the canvas. Everytime I click, I get to draw a new circle, but the circle drawn previously is gone. So, I tried to have lines for setting canvas outside drawCircle function, so it doesn't reset canvas whenever the function called. And now clicking doesn't draw a circle at all. Does having those lines outside the function affect something?

My second problem is that I want to have gradient circle, from 100% color to transparent, but it seems that the gradient is relative to the position of the circle in the canvas. For example, if I click on the left edge of the frame, the gradient comes out perfectly the way I want it, but as it gets more right, the the transparent part of the gradient gets bigger and once I click on the midst of the frame, I can't see the circle at all. And the transparent part of the gradient seems little black to me. What can I do to fix it?

Lastly, once I'm all good with figuring out how to draw multiples circles in the way I want, I ultimately have them fall down like one of snowflake stuff. Is there a function that could facilitate it?

Thank you very much!

Upvotes: 1

Views: 1786

Answers (1)

Blindman67
Blindman67

Reputation: 54026

You can create a new gradient set to the position of the circle each time you render a circle which can be slow. Or you can create a gradient at position 0,0 and a fixed radius and then use transform to move the gradient and circle to the correct position.

To animate many objects you create an array of them, looping through each items to update the position, and then to draw. You could do the update and draw in the same function but when things get more complex its best to separate the update and draw.

// get the 2D context
const ctx = canvas.getContext("2d");

// listen to click events adding ball for each click
canvas.addEventListener("click",(e)=>{ circles.add(e.pageX,e.pageY,20) });

// define a gradient for circles
const gradientRadius = 10;
const grad = ctx.createRadialGradient(0,0,0,0,0,gradientRadius); // gradient 10 pixels radius at 0,0
grad.addColorStop(1,"rgba(255,0,0,0)");
grad.addColorStop(0,"rgba(0,0,0,1)");

const gravity = 0.9; // gravity acceleration

// draws a circle using grad (above)
function drawCircle(x,y,radius){
    var scale = radius / gradientRadius;
    ctx.fillStyle = grad;
    ctx.setTransform(scale, 0, 0, scale, x, y);
    ctx.beginPath();
    ctx.arc(0, 0, radius / scale, 0, Math.PI * 2);
    ctx.fill();
}

// this object handles all circles. Circles are stored in the array circles.items
// the function update and draw both return the circles object so that they
// can be chained.
const circles = {
    items : [], // array of circles
    add(x,y,radius){
        var circle;
        circles.items.push(circle = {
            x,y,radius,
            dx : 0,  // delta x and y (movement per frame
            dy : 0, 
        });
        return circle;
    },
    update(){
        var i,c;
        for(i = 0; i < circles.items.length; i++){
            c = circles.items[i]; // get the circle
            c.dy += gravity;
            c.x += c.dx;
            c.y += c.dy;
            // simulate bounce.. This is the most basic, search SO for better methods
            if(c.y + c.radius > ctx.canvas.height){
                c.y = ctx.canvas.height - c.radius;
                c.dy = -Math.abs(c.dy);  // window resize may cause ball to be moving up when
                                         // it hits the bottom so need to ensure the bounce is
                                         // away from the bottom with Math.abs
            }
        }
        return circles;
    },
    draw(){
        var i,c;
        for(i = 0; i < circles.items.length; i++){
            c = circles.items[i]; // get the circle
            drawCircle(c.x,c.y,c.radius);
        }
        return circles;
    }
}

// main animation loop called once every 1/60th second (if possible)
// It checks if the widow size matches the canvas and will resize it
// if not.
// Then clears the canvas and updates and draws the circles.
// Then requests the next animation frame
function mainLoop(time){
    if(canvas.width !== innerWidth || canvas.height !== innerHeight){ // resize canvas if window size has changed
        canvas.width = innerWidth;
        canvas.height = innerHeight;
    }
    ctx.setTransform(1,0,0,1,0,0); // set default transform
    ctx.clearRect(0,0,canvas.width,canvas.height); // clear the canvas
    circles.update().draw();
    requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
   position : absolute;
   top : 0px;
   left : 0px;
}
Click to add circle.
<canvas id=canvas></canvas>

Upvotes: 3

Related Questions