title: HTML5 Canvas: Bouncing Balls with Image Overlay
body: <p>I'm really struggling with a couple problems in the HTML5 canvas.</p>
  
  <p>I've posted the project to GitHub pages (<a href="https://swedy13.github.io/" rel="nofollow noreferrer">https://swedy13.github.io/</a>) 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.</p>
  
  <p><a href="https://i.stack.imgur.com/PIMJ1.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/PIMJ1.png" alt="Bouncing circles in HTML5 Canvas"></a></p>
  
  <p>I'm calling requestAnimation from three files based on different actions, all of which can be found in <a href="https://github.com/swedy13/swedy13.github.io/tree/master/assets/js" rel="nofollow noreferrer">https://github.com/swedy13/swedy13.github.io/tree/master/assets/js</a></p>
  
  <p>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)</p>
  
  <p>Update: I've added the "portfolio.js" code below so the answer can be self-contained.</p>
  
  <pre><code>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 &gt; x &amp;&amp; x &gt;= 500) {
          cPos    = x * (x / y) - 150;
          cMargin = 150;
      }
  
      // Landscape Variables
      if (x &gt; 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 &lt; $('.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 &lt; 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 &gt; container.width) || (circles[i].x - circles[i].r + circles[i].vx &lt; 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 &gt; container.height) || (circles[i].y - circles[i].r + circles[i].vy &lt; 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);
      }
  }
  </code></pre>
  
  <p>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.</p>
  
  <p>So basically, I'm trying to figure out how to attach the background image to the circle (based on the circle ID).</p>
  
  <hr>
  
  <h2>----------------- UPDATE -----------------</h2>
  
  <p>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.</p>
  
  <pre><code>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 &lt; 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 &lt; 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 &gt; canvas.width) || (circle.xPos - circle.size + circle.xVel &lt; 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 &gt; canvas.height) || (circle.yPos + circle.yVel - circle.size &lt; 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;
  }
  }
  </code></pre>
  
  <p>I'm not sure if I'm animating the correct thing. I've tried animating canvas2, but that didn't make sense to me.</p>
  
  <p>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.</p>