swedy
swedy

Reputation: 21

HTML5 Canvas: Bouncing Balls with Image Overlay

I'm really struggling with a couple problems in the HTML5 canvas.

I've posted the project to GitHub pages (https://swedy13.github.io/) and added an image (the circles are in motion) so you can see the issue. Basically, if you scroll down you'll find several green circles bouncing around on the page. I'd like to replace those with my client logos.

Bouncing circles in HTML5 Canvas

I'm calling requestAnimation from three files based on different actions, all of which can be found in https://github.com/swedy13/swedy13.github.io/tree/master/assets/js

Filenames: - filters.js (calls requestAnimation when you use the filters) - main.js (on load and resize) - portfolio.js (this is where the canvas code is)

Update: I've added the "portfolio.js" code below so the answer can be self-contained.

function runAnimation(width, height, type){
    var canvas  = document.getElementsByTagName('canvas')[0];
    var c = canvas.getContext('2d');

    // ---- DIMENSIONS ---- //
    // Container
    var x = width;
    var y = height - 65;
    canvas.width  = x;
    canvas.height = y;
    var container = {x: 0 ,y: 0 ,width: x, height: y};

    // Portrait Variables
    var cPos    = 200;
    var cMargin = 70;
    var cSpeed  = 3;
    var r       = x*.075;

    if (y > x && x >= 500) {
        cPos    = x * (x / y) - 150;
        cMargin = 150;
    }

    // Landscape Variables
    if (x > y) {
        cPos    = y * (y / x) - 50;
        cMargin = 150;
        cSpeed  = 3;
        r       = x*.05;
    }


    // ---- CIRCLES ---- //
    // Circles
    var circles = [];
    var img = new Image();

    // Gets active post ids and count
    var activeName  = [];
    var activeLogo  = [];
    var activePosts = $('.active').map(function() {
        activeName.push($(this).text().replace(/\s+/g, '-').toLowerCase());

        // Returns the image source
        /*activeLogo.push($(this).find('img').prop('src'));*/

        // Returns an image node
        var elem = document.getElementsByClassName($(this).text().replace(/\s+/g, '-').toLowerCase())
                            activeLogo.push(elem[0].childNodes[0]);
        });

        // Populates circle data
        for (var i = 0; i < $('.active').length; i++) {
            circles.push({
            id:activeName[i],
            r:r,
            color: 100,
            /*image: activeLogo[i],*/
            x:Math.random() * cPos + cMargin,
            y:Math.random() * cPos + cMargin,
            vx:Math.random() * cSpeed + .25,
            vy:Math.random() * cSpeed + .25
        });
    }

    // ---- DRAW ---- //
    requestAnimationFrame(draw);
    function draw(){
        c.fillStyle = 'white';
        c.fillRect(container.x, container.y, container.width, container.height);

        for (var i = 0; i < circles.length; i++){
            /*var img = new Image();
            var path = circles[i].image;*/
            /*var size = circles[i].r * 2;*/
            /*img.src = circles[4].image;*/
            var img = activeLogo[i];
            img.onload = function (circles) {
                /*c.drawImage(img, 0, 0, size, size);*/

                var pattern = c.createPattern(this, "repeat");
                c.fillStyle = pattern;
                c.fill();
            };

            c.fillStyle = 'hsl(' + circles[i].color + ', 100%, 50%)';
            c.beginPath();
            c.arc(circles[i].x, circles[i].y, circles[i].r, 0, 2*Math.PI, false);
            c.fill();


            // If the circle size/position is greater than the canvas width, bounce x
            if ((circles[i].x + circles[i].vx + circles[i].r > container.width) || (circles[i].x - circles[i].r + circles[i].vx < container.x)) {
                circles[i].vx = -circles[i].vx;
            }

            // If the circle size/position is greater than the canvas width, bounce y
            if ((circles[i].y + circles[i].vy + circles[i].r > container.height) || (circles[i].y - circles[i].r + circles[i].vy < container.y)){
                circles[i].vy = -circles[i].vy;
            }

            // Generates circle motion by adding position and velocity each frame
            circles[i].x += circles[i].vx;
            circles[i].y += circles[i].vy;
        }
        requestAnimationFrame(draw);
    }
}

The way it works right now is: 1. I have my portfolio content set to "display: none" (eventually it will be a pop-up when they click on one of the circles). 2. The canvas is getting the portfolio objects from the DOM, including the image that I can't get to work. 3. If I use the "onload()" function, I can get the images to show up and repeat in the background. But it's just a static background - the circles are moving above it and revealing the background. That isn't what I want.

So basically, I'm trying to figure out how to attach the background image to the circle (based on the circle ID).


----------------- UPDATE -----------------

I can now clip the image to a circle and get the circle to move in the background. But it isn't visible on the page (I can tell it's moving by console logging it's position). The only time I see anything is when the circle lines up with the images position, then it shows.

function runAnimation(width, height, type){
    var canvas  = document.getElementsByTagName('canvas')[0];
    var c = canvas.getContext("2d");
    canvas.width = width;
    canvas.height = height;

    // Collects portfolio information from the DOM
    var activeName = [];
    var activeLogo = [];
    $('.active').map(function() {
        var text = $(this).text().replace(/\s+/g, '-').toLowerCase();
        var elem = document.getElementsByClassName(text);

        activeName.push(text);
        activeLogo.push(elem[0].childNodes[0]);
    });

    var img = new Image();
    img.onload = start;

    var circles = [];
    var cPos = 200;
    var cMargin = 70;
    var cSpeed = 3;
    for (var i = 0; i < 1; i++) {
        circles.push({
            id: activeName[i],
            img: activeLogo[i],
            size: 50,
            xPos: Math.random() * cPos + cMargin,
            yPos: Math.random() * cPos + cMargin,
            xVel: Math.random() * cSpeed + .25,
            yVel: Math.random() * cSpeed + .25,
        });
        img.src = circles[i].img;
    }

    requestAnimationFrame(start);
    function start(){
        for (var i = 0; i < circles.length; i++) {
            var circle = createImageInCircle(circles[i].img, circles[i].size, circles[i].xPos, circles[i].yPos);
            c.drawImage(circle, circles[i].size, circles[i].size);
                                            animateCircle(circles[i]);
        }

        requestAnimationFrame(start);
    }

    function createImageInCircle(img, radius, x, y){
        var canvas2 = document.createElement('canvas');
        var c2 = canvas2.getContext('2d');

        canvas2.width = canvas2.height = radius*2;

        c2.fillStyle = 'white';
        c2.beginPath();
        c2.arc(x, y, radius, 0, Math.PI*2);
        c2.fill();
        c2.globalCompositeOperation = 'source-atop';
        c2.drawImage(img, 0, 0, 100, 100);

        return(canvas2);
    }

    function animateCircle(circle) {
    // If the circle size/position is greater than the canvas width, bounce x
    if ((circle.xPos + circle.xVel + circle.size > canvas.width) || (circle.xPos - circle.size + circle.xVel < 0)) {
        console.log('Bounce X');
        circle.xVel = -circle.xVel;
    }

    // If the circle size/position is greater than the canvas width, bounce y
    if ((circle.yPos + circle.yVel + circle.size > canvas.height) || (circle.yPos + circle.yVel - circle.size < 0)) {
        console.log('Bounce Y');
        circle.yVel = -circle.yVel;
    }

    // Generates circle motion by adding position and velocity each frame
    circle.xPos += circle.xVel;
    circle.yPos += circle.yVel;
}
}

I'm not sure if I'm animating the correct thing. I've tried animating canvas2, but that didn't make sense to me.

PS - Sorry for the GitHub formatting, not sure why it looks like that. PPS - Apologies for any junk code I didn't clean up. I've tried a lot of stuff and probably lost track of some of the changes. PPPS - And forgive me for not making the answer self-contained. I thought linking to GitHub would be more useful, but I've updated the question to contain all the necessary info. Thanks for the feedback.

Upvotes: 1

Views: 1875

Answers (1)

markE
markE

Reputation: 105015

To get you started...

Here's how to clip an image into a circle using compositing.

The example code creates a single canvas logo-ball that you can reuse for each of your bouncing balls.

var logoball1=dreateImageInCircle(logoImg1,50);
var logoball2=dreateImageInCircle(logoImg2,50);

Then you can draw each logo-ball onto your main canvas like this:

ctx.drawImage(logoball1,35,40);
ctx.drawImage(logoball2,100,75);

There are many examples here on Stackoverflow of how to animate the balls around the canvas so I leave that part to you.

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/m%26m600x455.jpg";
function start(){
    var copy=createImageInCircle(img,50);
    ctx.drawImage(copy,20,75);
    ctx.drawImage(copy,150,120);
    ctx.drawImage(copy,280,75);
}

function createImageInCircle(img,radius){
    var c=document.createElement('canvas');
    var cctx=c.getContext('2d');
    c.width=c.height=radius*2;
    cctx.beginPath();
    cctx.arc(radius,radius,radius,0,Math.PI*2);
    cctx.fill();
    cctx.globalCompositeOperation='source-atop';
    cctx.drawImage(img,radius-img.width/2,radius-img.height/2);
    return(c);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>

Upvotes: 2

Related Questions