Smeegs
Smeegs

Reputation: 9224

Trying to create a confetti effect in html5, how do I get a different fill color for each element?

EDIT:

For anybody who is curious, here is the finished result.

https://jsfiddle.net/Javalsu/q1kmLoja/4/


I'm building off of the code I found in this link

http://thecodeplayer.com/walkthrough/html5-canvas-snow-effect

I want to make this more of a confetti falling effect than a snow effect, and I would need to make each element a different color. But it seems that the fill color is set for entire canvas at once.

Is there a way to specify a different fill color for each element or am I going about this the entirely wrong way?

Thanks

Update: Here is the finished product if anybody has a need for confetti

http://jsfiddle.net/mj3SM/6/

window.onload = function () {
//canvas init
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

//canvas dimensions
var W = window.innerWidth;
var H = window.innerHeight;
canvas.width = W;
canvas.height = H;

//snowflake particles
var mp = 200; //max particles
var particles = [];
for (var i = 0; i < mp; i++) {
    particles.push({
        x: Math.random() * W, //x-coordinate
        y: Math.random() * H, //y-coordinate
        r: Math.random() * 15 + 1, //radius
        d: Math.random() * mp, //density
        color: "rgba(" + Math.floor((Math.random() * 255)) + ", " + Math.floor((Math.random() * 255)) + ", " + Math.floor((Math.random() * 255)) + ", 0.8)",
        tilt: Math.floor(Math.random() * 5) - 5
    });
}

//Lets draw the flakes
function draw() {
    ctx.clearRect(0, 0, W, H);



    for (var i = 0; i < mp; i++) {
        var p = particles[i];
        ctx.beginPath();
        ctx.lineWidth = p.r;
        ctx.strokeStyle = p.color; // Green path
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(p.x + p.tilt + p.r / 2, p.y + p.tilt);
        ctx.stroke(); // Draw it
    }

    update();
}

//Function to move the snowflakes
//angle will be an ongoing incremental flag. Sin and Cos functions will be applied to it to create vertical and horizontal movements of the flakes
var angle = 0;

function update() {
    angle += 0.01;
    for (var i = 0; i < mp; i++) {
        var p = particles[i];
        //Updating X and Y coordinates
        //We will add 1 to the cos function to prevent negative values which will lead flakes to move upwards
        //Every particle has its own density which can be used to make the downward movement different for each flake
        //Lets make it more random by adding in the radius
        p.y += Math.cos(angle + p.d) + 1 + p.r / 2;
        p.x += Math.sin(angle) * 2;

        //Sending flakes back from the top when it exits
        //Lets make it a bit more organic and let flakes enter from the left and right also.
        if (p.x > W + 5 || p.x < -5 || p.y > H) {
            if (i % 3 > 0) //66.67% of the flakes
            {
                particles[i] = {
                    x: Math.random() * W,
                    y: -10,
                    r: p.r,
                    d: p.d,
                    color: p.color,
                    tilt: p.tilt
                };
            } else {
                //If the flake is exitting from the right
                if (Math.sin(angle) > 0) {
                    //Enter from the left
                    particles[i] = {
                        x: -5,
                        y: Math.random() * H,
                        r: p.r,
                        d: p.d,
                        color: p.color,
                        tilt: p.tilt
                    };
                } else {
                    //Enter from the right
                    particles[i] = {
                        x: W + 5,
                        y: Math.random() * H,
                        r: p.r,
                        d: p.d,
                        color: p.color,
                        tilt: p.tilt
                    };
                }
            }
        }
    }
}

//animation loop
setInterval(draw, 20);

}

Upvotes: 30

Views: 25977

Answers (4)

DaWe
DaWe

Reputation: 1702

I think canvas-confetti is more realistic than any others here, and it has more feature: confetti, snow, fireworks, etc. Click here for demo!

Upvotes: 0

WeisserHund
WeisserHund

Reputation: 160

Here is a version based on the post by Niels, I wanted a reusable object that I can call and add to any page.

Usage:

confetti.Init(#IdofContainer(div)#, 50,25,100)

Code:

var confetti = {
angle: 0,
ctx: 0,
H: 0,
W: 0,
mp: 0,
particles: [],
endFunction: '',
Init: function (parent, maxParticles, iCount, speed, endFunct) {
    confetti.stopped = false;
    confetti.runner = null;
    confetti.endFunction = endFunct;
    var canvas = document.getElementById("confettiCanvasId");
    if (canvas) {
        canvas.parentNode.removeChild(canvas);
    }
    canvas = document.createElement('canvas');
    canvas.className = 'confettiCanvas';
    canvas.id = 'confettiCanvasId'
    $id(parent).appendChild(canvas);
    var ctx = canvas.getContext("2d");
    var W = $id(parent).clientHeight;
    var H = $id(parent).clientWidth;
    canvas.width = W;
    canvas.height = H;
    confetti.particles = [];
    for (var i = 0; i < maxParticles; i++) {
        confetti.particles.push({
            x: Math.random() * W,
            y: Math.random() * H,
            r: Math.random() * 4 + 1, //radius
            d: Math.random() * maxParticles, //density
            color: "rgba(" + Math.floor((Math.random() * 255)) + ", " + Math.floor((Math.random() * 255)) + ", " + Math.floor((Math.random() * 255)) + ", 0.8)"
        });
    }
    myCounter = new confetti.Counter({
        seconds: iCount,
        speed: speed,
        onUpdateStatus: function (sec) {
            $l(Math.random() * 255)
            ctx.clearRect(0, 0, W, H);
            for (var i = 0; i < maxParticles; i++) {
                var p = confetti.particles[i];
                ctx.beginPath();
                ctx.fillStyle = p.color;
                ctx.moveTo(p.x, p.y);
                ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2, true);
                ctx.fill();
            }

            confetti.angle += 0.01;
            for (var i = 0; i < maxParticles; i++) {
                var p = confetti.particles[i];
                p.y += Math.cos(confetti.angle + p.d) + 1 + p.r / 2;
                p.x += Math.sin(confetti.angle) * 2;
                if (p.x > W + 5 || p.x < -5 || p.y > H) {
                    if (i % 3 > 0) //66.67% of the flakes
                    {
                        confetti.particles[i] = {x: Math.random() * W, y: -10, r: p.r, d: p.d, color: p.color};
                    }
                    else {
                        if (Math.sin(confetti.angle) > 0) {
                            confetti.particles[i] = {x: -5, y: Math.random() * H, r: p.r, d: p.d, color: p.color};
                        }
                        else {
                            confetti.particles[i] = {x: W + 5, y: Math.random() * H, r: p.r, d: p.d, color: p.color};
                        }
                    }
                }
            }
        },
        onCounterEnd: function () {
            stopTimer();
            myCounter.stop();
            confetti.Stop();
        }});
    myCounter.start();
},
FadeOut:function fadeOut() {
    var alpha = 1.0;   // full opacity
    for (var i = 0; i < confetti.particles.length; i++) {
        var p = confetti.particles[i];
        interval = setInterval(function () {
            //confetti.canvas.width = confetti.canvas.width; // Clears the canvas
            p.color = "rgba(255, 0, 0, " + alpha + ")";
            alpha = alpha - 0.05; // decrease opacity (fade out)
            if (alpha < 0) {
                //confetti.canvas.width = confetti.canvas.width;
                clearInterval(interval);
            }
        }, 50);


    }
        },
Counter: function Countdown(options) {
    var timer,
        instance = this,
        seconds = options.seconds || 10,
        updateStatus = options.onUpdateStatus || function () {
        },
        counterEnd = options.onCounterEnd || function () {
        };

    function decrementCounter() {
        updateStatus(seconds);
        if (seconds === 0) {
            counterEnd();
            instance.stop();
        }
        seconds--;
    }

    this.start = function () {
        clearInterval(timer);
        timer = 0;
        seconds = options.seconds;
        timer = setInterval(decrementCounter, options.speed);
    };

    this.stop = function () {
        clearInterval(timer);
    };
},
Stop: function stop() {
    $('#confettiCanvasId').fadeOut();
    setTimeout(function(){
        var canvas = document.getElementById("confettiCanvasId");
        if (canvas) {
            canvas.parentNode.removeChild(canvas);
        }
        if (confetti.endFunction) {
            confetti.endFunction();
        }
    },1000);

}
};

CSS:

.confettiCanvas{
overflow: hidden;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
}

Upvotes: 1

Simon Sarris
Simon Sarris

Reputation: 63862

Great question. Consider the drawing loop for the sample:

ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
ctx.beginPath();
for(var i = 0; i < mp; i++)
{
  var p = particles[i];
  ctx.moveTo(p.x, p.y);
  ctx.arc(p.x, p.y, p.r, 0, Math.PI*2, true);
}
ctx.fill();

It is making one path, adding many arcs, and then filling it one time.

To change it you will need to fill it once per particle instead. You'll also want to give each particle a unique color:

for (var i = 0; i < mp; i++) {
    var p = particles[i];
    ctx.fillStyle = p.color;
    ctx.beginPath();        
    ctx.moveTo(p.x, p.y);
    ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2, true);
    ctx.fill();
}

Note how beginPath() and fill() are now inside the loop. This is important, because each arc needs its own path and fill. This is much slower than making them all with one path, but is necessary if you want different colored particles.

That p.color:

particles.push({
    x: Math.random() * W, //x-coordinate
    y: Math.random() * H, //y-coordinate
    r: Math.random() * 4 + 1, //radius
    d: Math.random() * mp, //density

    // I'm new!
    color: "rgba(" + Math.floor(Math.random()*255) +
            ", " + Math.floor(Math.random()*255) + ", 255, 0.8)"
})

Here's a working example:

http://jsfiddle.net/j4NZK/1/

Upvotes: 3

Niels
Niels

Reputation: 49919

Try it like this: http://jsfiddle.net/vxP5q/

The JS:

window.onload = function(){
//canvas init
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

//canvas dimensions
var W = window.innerWidth;
var H = window.innerHeight;
canvas.width = W;
canvas.height = H;

//snowflake particles
var mp = 25; //max particles
var particles = [];
for(var i = 0; i < mp; i++)
{
    particles.push({
        x: Math.random()*W, //x-coordinate
        y: Math.random()*H, //y-coordinate
        r: Math.random()*4+1, //radius
        d: Math.random()*mp, //density
        color: "rgba(" + Math.floor((Math.random() * 255)) +", " + Math.floor((Math.random() * 255)) +", " + Math.floor((Math.random() * 255)) + ", 0.8)"
    })
}

//Lets draw the flakes
function draw()
{
    ctx.clearRect(0, 0, W, H);



    for(var i = 0; i < mp; i++)
    { 
        var p = particles[i];
        ctx.beginPath();
        ctx.fillStyle = p.color;
        ctx.moveTo(p.x, p.y);
        ctx.arc(p.x, p.y, p.r, 0, Math.PI*2, true);
        ctx.fill();
    }

    update();
}

//Function to move the snowflakes
//angle will be an ongoing incremental flag. Sin and Cos functions will be applied to it to create vertical and horizontal movements of the flakes
var angle = 0;
function update()
{
    angle += 0.01;
    for(var i = 0; i < mp; i++)
    {
        var p = particles[i];
        //Updating X and Y coordinates
        //We will add 1 to the cos function to prevent negative values which will lead flakes to move upwards
        //Every particle has its own density which can be used to make the downward movement different for each flake
        //Lets make it more random by adding in the radius
        p.y += Math.cos(angle+p.d) + 1 + p.r/2;
        p.x += Math.sin(angle) * 2;

        //Sending flakes back from the top when it exits
        //Lets make it a bit more organic and let flakes enter from the left and right also.
        if(p.x > W+5 || p.x < -5 || p.y > H)
        {
            if(i%3 > 0) //66.67% of the flakes
            {
                particles[i] = {x: Math.random()*W, y: -10, r: p.r, d: p.d, color : p.color};
            }
            else
            {
                //If the flake is exitting from the right
                if(Math.sin(angle) > 0)
                {
                    //Enter from the left
                    particles[i] = {x: -5, y: Math.random()*H, r: p.r, d: p.d, color: p.color};
                }
                else
                {
                    //Enter from the right
                    particles[i] = {x: W+5, y: Math.random()*H, r: p.r, d: p.d, color : p.color};
                }
            }
        }
    }
}

//animation loop
setInterval(draw, 33);
}

What I've done. Where the pixels are generated I've added an unique (random) color. Where the update is, I'm making sure the colors are changed and where its drawn I've changed it so that it will create an inuque path for each confetti item.

Upvotes: 6

Related Questions