title: forEach inside Ticker makes Canvas slow
body: <p>I'm creating a HTML5 minigame that uses collision-detection and I've recently discovered that it has a speed problem:</p>
  
  <p><strong>I think the reason of this problem is that...</strong></p>
  
  <p>Inside the 60fps ticker there are two <code>forEach</code> loops, one for the <code>rects</code> array and other for the <code>lasers</code> array. So, when there's 5 rects and 5 lasers in the canvas, it'll loop 5 times in the first <code>forEach</code> and five times in the second <strong>at each frame</strong>, and each <code>forEach</code> function has lots of <code>ifs</code> in it, making the game slow. How can I change that to something less <em>CPU-intensive</em>?</p>
  
  <p><sub>If you know a bigger speed problem in this minigame, feel free to help me to solve it too.</sub></p>
  
  <hr>
  
  <p><strong>Here's my entire code:</strong></p>
  
  <p><sub>I highly recommend you to see the <a href="http://jsfiddle.net/dNm9V/1/" rel="nofollow">JSFiddle</a> instead of the code below, since there's more than 400 lines.</sub></p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
  
  &lt;head&gt;
      &lt;title&gt;VelJS α&lt;/title&gt;
  
      &lt;!-- This app was coded by Tiago Marinho --&gt;
      &lt;!-- Do not leech it! --&gt;
  
      &lt;link rel="shortcut icon" href="http://i.imgur.com/Jja8mvg.png"&gt;
  
      &lt;!-- EaselJS: --&gt;
      &lt;script src="http://static.tumblr.com/uzcr0ts/uzIn1l1v2/easeljs-0.7.1.min.js"&gt;&lt;/script&gt;
  
      &lt;script src="http://pastebin.com/raw.php?i=W4S2mtCp"&gt;&lt;/script&gt;
  
      &lt;!-- jQuery: --&gt;
      &lt;script src="http://code.jquery.com/jquery-1.11.0.min.js"&gt;&lt;/script&gt;
  
      &lt;script&gt;
          (function () {
  
              // Primary vars (stage, circle, rects):
              var stage,
                  circle, // Hero!
                  rects = [], // Platforms
                  lasers = [];
              // Velocity vars:
              var xvel = 0, // X Velocity
                  yvel = 0, // Y Velocity
                  xvelpast = 0,
                  yvelpast = 0;
              // Keyvars (up, left, right, down):
              var up = false, // W or arrow up
                  left = false, // A or arrow left
                  right = false, // D or arrow right
                  down = false; // S or arrow down
              // Other vars (maxvel, col, pause):
              var maxvel = 256, // Maximum velocity
                  col = false, // Collision detection helper (returns true if collided side-to-side)
                  pause = false;
              // Volatility vars (rmdir, pastrmdir):
              var rmdir = 0,
                  pastrmdir = 0;
  
              // Main part (aka creating stage, creating circle, etc):
  
              function init() {
                  stage = new createjs.Stage("canvas");
  
                  // Creating circle:
                  var circle = new createjs.Shape();
                  circle.radius = 11;
                  circle.graphics.beginFill("#fff").beginStroke("white").drawCircle(circle.radius - 0.5, circle.radius - 0.5, circle.radius);
                  circle.width = circle.radius * 2;
                  circle.height = circle.radius * 2;
                  stage.addChild(circle);
  
                  setTimeout(function () {
  
                      // newobj(W, H, X, Y)
                      newobj("laser", 3, 244, stage.canvas.width / 2 - 125, stage.canvas.height / 4 * 3 - 247);
                      newobj("rect", 125, 3, stage.canvas.width / 2 - 125, stage.canvas.height / 4 * 3 - 250);
                  }, 250); // Wait until first tick finishes and stage is resized to 100%, then calculate the middle of canvas.
  
                  // User Input (Redirect input to Input Handler):
  
                  // Keydown:
                  document.addEventListener("keydown", function (evt) {
                      if (evt.keyCode == 87 || evt.keyCode == 38) { // up
                          up = true;
                      }
                      if (evt.keyCode == 65 || evt.keyCode == 37) { // left
                          left = true;
                      }
                      if (evt.keyCode == 68 || evt.keyCode == 39) { // right
                          right = true;
                      }
                      if (evt.keyCode == 83 || evt.keyCode == 40) { // down
                          down = true;
                      }
                      if (evt.keyCode == 8 || evt.keyCode == 80) { // del/p
                          if (pause == false) {
                              xvelpast = xvel;
                              yvelpast = yvel;
                              pause = true;
  
                              var fadestep = 0;
                              for (var i = 1; i &gt; 0; i -= 0.1) {
                                  i = parseFloat(i.toFixed(1));
                                  fadestep++;
                                  fadeFill("circle", i, fadestep);
                                  rects.forEach(function (rect) {
                                      fadeFill("rect", i, fadestep);
                                  });
                              }
                          } else {
                              pause = false;
                              xvel = xvelpast;
                              yvel = yvelpast;
                              var fadestep = 0;
                              for (var i = 0; i &lt;= 1; i += 0.1) {
                                  i = parseFloat(i.toFixed(1));
                                  fadestep++;
                                  fadeFill("circle", i, fadestep);
                                  rects.forEach(function (rect) {
                                      fadeFill("rect", i, fadestep);
                                  });
                              }
                          }
                      }
                  });
                  // Keyup:
                  document.addEventListener("keyup", function (evt) {
                      if (evt.keyCode == 87 || evt.keyCode == 38) { // up
                          up = false;
                      }
                      if (evt.keyCode == 65 || evt.keyCode == 37) { // left
                          left = false;
                      }
                      if (evt.keyCode == 68 || evt.keyCode == 39) { // right
                          right = false;
                      }
                      if (evt.keyCode == 83 || evt.keyCode == 40) { // down
                          down = false;
                      }
                  });
  
                  // Functions:
  
                  // Fade beginFill to a lower alpha:
                  function fadeFill(obj, i, t) {
                      setTimeout(function () {
                          if (obj == "circle") {
                              circle.graphics.clear().beginFill("rgba(255,255,255," + i + ")").beginStroke("white").drawCircle(circle.radius, circle.radius, circle.radius).endFill();
                          }
                          if (obj == "rect") {
                              for (var r = 0; r &lt; rects.length; r++) {
                                  rects[r].graphics.clear().beginFill("rgba(255,255,255," + i + ")").beginStroke("white").drawRect(0, 0, rects[r].width, rects[r].height).endFill();
                              }
                          }
                      }, t * 20);
                  };
                  // To create new rects:
                  function newobj(type, w, h, x, y) {
                      if (type == "rect") {
                          var rect = new createjs.Shape();
                          rect.graphics.beginFill("#fff").beginStroke("white").drawRect(0, 0, w, h);
                          rect.width = w + 1;
                          rect.height = h + 1;
                          rect.y = Math.round(y) + 0.5;
                          rect.x = Math.round(x) + 0.5;
                          stage.addChild(rect);
                          rects.push(rect);
                      }
                      if (type == "laser") {
                          var laser = new createjs.Shape();
                          if (w &gt;= h) {
                              laser.graphics.beginFill("#c22").drawRect(0, 0, w, 1);
                              laser.width = w;
                              laser.height = 1;
                          } else {
                              laser.graphics.beginFill("#c22").drawRect(0, 0, 1, h);
                              laser.width = 1;
                              laser.height = h;
                          }
                          laser.shadow = new createjs.Shadow("#ff0000", 0, 0, 5);
                          laser.y = Math.round(y);
                          laser.x = Math.round(x);
                          stage.addChild(laser);
                          lasers.push(laser);
                      }
                  }
                  // Collision recoil:
                  function cls(clsdir) {
                      if (clsdir == "top") {
                          if (yvel &lt;= 4) {
                              yvel = 0;
                          } else {
                              yvel = Math.round(yvel * -0.5);
                          }
                      }
                      if (clsdir == "left") {
                          if (xvel &lt;= 4) {
                              xvel = 0;
                          } else {
                              xvel = Math.round(xvel * -0.5);
                          }
                      }
                      if (clsdir == "right") {
                          if (xvel &gt;= -4) {
                              xvel = 0;
                          } else {
                              xvel = Math.round(xvel * -0.5);
                          }
                      }
                      if (clsdir == "bottom") {
                          if (yvel &gt;= -4) {
                              yvel = 0;
                          } else {
                              yvel = Math.round(yvel * -0.5);
                          }
                      }
                      col = true;
                  }
                  // Die:
                  function die() {
                      circle.alpha = 1;
                      createjs.Tween.get(circle).to({
                          alpha: 0
                      }, 250).call(handleComplete);
  
                      function handleComplete() {
                          circle.x = stage.canvas.width / 2 - circle.radius;
                          circle.y = stage.canvas.height / 2 - circle.radius;
                          createjs.Tween.get(circle).to({
                              alpha: 1
                          }, 250);
                          yvel = 0;
                          xvel = 0;
                          yvelpast = 0;
                          xvelpast = 0;
                      }
                      yvel = yvel/2;
                      xvel = xvel/2;
                  }
  
                  // Set Intervals:
  
                  // Speed/Score:
                  setInterval(function () {
                      if (pause == false) {
                          speed = Math.abs(xvel) + Math.abs(yvel);
                          $(".speed").html("Speed: " + speed);
                      } else {
                          speed = Math.abs(xvelpast) + Math.abs(yvelpast);
                          $(".speed").html("Speed: " + speed + " (Paused)");
                      }
                  }, 175);
  
                  // Tick:
  
                  createjs.Ticker.on("tick", tick);
                  createjs.Ticker.setFPS(60);
  
                  function tick(event) {
  
                      // Input Handler:
  
                      if (up == true) {
                          yvel -= 2;
                      } else {
                          if (yvel &lt; 0) {
                              yvel++;
                          }
                      }
                      if (left == true) {
                          xvel -= 2;
                      } else {
                          if (xvel &lt; 0) {
                              xvel++;
                          }
                      }
                      if (right == true) {
                          xvel += 2;
                      } else {
                          if (xvel &gt; 0) {
                              xvel--;
                          }
                      }
                      if (down == true) {
                          yvel += 2;
                      } else {
                          if (yvel &gt; 0) {
                              yvel--;
                          }
                      }
  
                      // Volatility:
  
                      pastrmdir = rmdir;
                      rmdir = Math.floor((Math.random() * 20) + 1);
                      if (rmdir == 1 &amp;&amp; pastrmdir != 4) {
                          yvel--;
                      }
                      if (rmdir == 2 &amp;&amp; pastrmdir != 3) {
                          xvel--;
                      }
                      if (rmdir == 3 &amp;&amp; pastrmdir != 2) {
                          xvel++;
                      }
                      if (rmdir == 4 &amp;&amp; pastrmdir != 1) {
                          yvel++;
                      }
  
                      // Velocity limiter:
  
                      if (xvel &gt; maxvel || xvel &lt; maxvel * -1) {
                          (xvel &gt; 0) ? xvel = maxvel : xvel = maxvel * -1;
                      }
                      if (yvel &gt; maxvel || yvel &lt; maxvel * -1) {
                          (yvel &gt; 0) ? yvel = maxvel : yvel = maxvel * -1;
                      }
  
                      // Collision handler:
                      // xvel and yvel modifications must be before this!
  
                      rects.forEach(function (rect) { // Affect all rects
  
                          // Collision detection:
                          // (This MUST BE after every change in xvel/yvel)
  
                          // Next circle position calculation:
                          nextposx = circle.x + event.delta / 1000 * xvel * 30,
                          nextposy = circle.y + event.delta / 1000 * yvel * 30;
                          // Collision between objects (Rect and Circle):
                          if (nextposy + circle.height &gt; rect.y &amp;&amp; circle.y + circle.height &lt; rect.y &amp;&amp; circle.x + circle.width &gt; rect.x &amp;&amp; circle.x &lt; rect.x + rect.width) {
                              cls("top");
                          }
                          if (nextposx + circle.width &gt; rect.x &amp;&amp; circle.x + circle.width &lt; rect.x &amp;&amp; circle.y + circle.height &gt; rect.y &amp;&amp; circle.y &lt; rect.y + rect.height) {
                              cls("left");
                          }
                          if (nextposx &lt; rect.x + rect.width &amp;&amp; circle.x &gt; rect.x + rect.width &amp;&amp; circle.y + circle.height &gt; rect.y &amp;&amp; circle.y &lt; rect.y + rect.height) {
                              cls("right");
                          }
                          if (nextposy &lt; rect.y + rect.height &amp;&amp; circle.y &gt; rect.y + rect.height &amp;&amp; circle.x + circle.width &gt; rect.x &amp;&amp; circle.x &lt; rect.x + rect.width) {
                              cls("bottom");
                          }
                          rects.forEach(function (rect) {
                              // Check side-to-side collisions with other rects:
                              if (nextposy + circle.height &gt; rect.y &amp;&amp; circle.y + circle.height &lt; rect.y &amp;&amp; circle.x + circle.width &gt; rect.x &amp;&amp; circle.x &lt; rect.x + rect.width) {
                                  col = true;
                              }
                              if (nextposx + circle.width &gt; rect.x &amp;&amp; circle.x + circle.width &lt; rect.x &amp;&amp; circle.y + circle.height &gt; rect.y &amp;&amp; circle.y &lt; rect.y + rect.height) {
                                  col = true;
                              }
                              if (nextposx &lt; rect.x + rect.width &amp;&amp; circle.x &gt; rect.x + rect.width &amp;&amp; circle.y + circle.height &gt; rect.y &amp;&amp; circle.y &lt; rect.y + rect.height) {
                                  col = true;
                              }
                              if (nextposy &lt; rect.y + rect.height &amp;&amp; circle.y &gt; rect.y + rect.height &amp;&amp; circle.x + circle.width &gt; rect.x &amp;&amp; circle.x &lt; rect.x + rect.width) {
                                  col = true;
                              }
                          });
  
                          // Edge-to-edge collision between objects (Rect and Circle) - Note that this will not occur if a side-to-side collision occurred in the current frame!:
                          if (nextposy + circle.height &gt; rect.y &amp;&amp;
                              nextposx + circle.width &gt; rect.x &amp;&amp;
                              nextposx &lt; rect.x + rect.width &amp;&amp;
                              nextposy &lt; rect.y + rect.height &amp;&amp;
                              col == false) {
                              if (circle.y + circle.height &lt; rect.y &amp;&amp;
                                  circle.x + circle.width &lt; rect.x) {
                                  cls("top");
                                  cls("left");
                              }
                              if (circle.y &gt; rect.y + rect.height &amp;&amp;
                                  circle.x + circle.width &lt; rect.x) {
                                  cls("bottom");
                                  cls("left");
                              }
                              if (circle.y + circle.height &lt; rect.y &amp;&amp;
                                  circle.x &gt; rect.x + rect.width) {
                                  cls("top");
                                  cls("right");
                              }
                              if (circle.y &gt; rect.y + rect.height &amp;&amp;
                                  circle.x &gt; rect.x + rect.width) {
                                  cls("bottom");
                                  cls("right");
                              }
                          }
                          col = false;
  
                          // Stage collision:
                          if (nextposy &lt; 0) { // Collided with TOP of stage. Trust me.
                              cls("bottom"); // Inverted clsdir is proposital!
                          }
                          if (nextposx &lt; 0) {
                              cls("right");
                          }
                          if (nextposx + circle.width &gt; stage.canvas.width) {
                              cls("left");
                          }
                          if (nextposy + circle.height &gt; stage.canvas.height) {
                              cls("top");
                          }
                      });
  
                      // Laser collision handler:
                      lasers.forEach(function (laser) {
                          laser.alpha = Math.random() + 0.5;
                          nextposx = circle.x + event.delta / 1000 * xvel * 30,
                          nextposy = circle.y + event.delta / 1000 * yvel * 30;
  
                          if (nextposy + circle.height &gt; laser.y &amp;&amp; circle.y + circle.height &lt; laser.y &amp;&amp; circle.x + circle.width &gt; laser.x &amp;&amp; circle.x &lt; laser.x + laser.width) {
                              circle.y = laser.y-circle.height;
                              die();
                          }
                          if (nextposx + circle.width &gt; laser.x &amp;&amp; circle.x + circle.width &lt; laser.x &amp;&amp; circle.y + circle.height &gt; laser.y &amp;&amp; circle.y &lt; laser.y + laser.height) {
                              circle.x = laser.x-circle.width;
                              die();
                          }
                          if (nextposx &lt; laser.x + laser.width &amp;&amp; circle.x &gt; laser.x + laser.width &amp;&amp; circle.y + circle.height &gt; laser.y &amp;&amp; circle.y &lt; laser.y + laser.height) {
                              circle.x = laser.x+laser.width;
                              die();
                          }
                          if (nextposy &lt; laser.y + laser.height &amp;&amp; circle.y &gt; laser.y + laser.height &amp;&amp; circle.x + circle.width &gt; laser.x &amp;&amp; circle.x &lt; laser.x + laser.width) {
                              circle.y = laser.y+laser.height;
                              die();
                          }
                      });
  
                      // Velocity:
                      if (pause == true) {
                          xvel = 0;
                          yvel = 0;
                      }
                      circle.x += event.delta / 1000 * xvel * 20;
                      circle.y += event.delta / 1000 * yvel * 20;
  
                      // Stage.canvas 100% width and height:
  
                      stage.canvas.width = window.innerWidth;
                      stage.canvas.height = window.innerHeight;
  
                      // Update stage:
  
                      stage.update(event);
                  }
                  setTimeout(function () {
                      // Centre circle:
                      circle.x = stage.canvas.width / 2 - circle.radius;
                      circle.y = stage.canvas.height / 2 - circle.radius;
  
                      // Fade-in after loading:
  
                      $(".speed").css({
                          opacity: 1
                      });
                      $("canvas").css({
                          opacity: 1
                      });
                  }, 500);
              }
              $(function () {
                  init();
              });
          })();
      &lt;/script&gt;
      &lt;style&gt;
          * {
              margin: 0;
          }
          html,
          body {
              -webkit-font-smoothing: antialiased;
              font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
              font-weight: 300;
              color: #fff;
              background-color: #181818
          }
          .build {
              position: absolute;
              bottom: 5px;
              right: 5px;
              color: rgba(255, 255, 255, 0.05)
          }
          canvas {
              -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
              filter: alpha(opacity=0);
              opacity: 0;
              position: absolute;
              top: 0;
              left: 0;
              -moz-transition: 5s ease;
              -o-transition: 5s ease;
              -webkit-transition: 5s ease;
              transition: 5s ease
          }
          .speed {
              -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
              filter: alpha(opacity=0);
              opacity: 0;
              position: absolute;
              top: 5px;
              left: 5px;
              color: #fff;
              font-size: 16px;
              -moz-transition: 5s ease;
              -o-transition: 5s ease;
              -webkit-transition: 5s ease;
              transition: 5s ease
          }
          h2 {
              text-align: center;
              font-size: 22px;
              font-weight: 700
          }
          p {
              font-size: 16px;
              margin: 0
          }
      &lt;/style&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
      &lt;p class="speed"&gt;&lt;/p&gt;
      &lt;p class="build"&gt;α256&lt;/p&gt;
      &lt;canvas id="canvas"&gt;
          &lt;h2&gt;Your browser doesn't support Canvas.&lt;/h2&gt;
          &lt;p&gt;Switch to &lt;b&gt;Chrome 33&lt;/b&gt;, &lt;b&gt;Firefox 27&lt;/b&gt; or &lt;b&gt;Safari 7&lt;/b&gt;.&lt;/p&gt;
      &lt;/canvas&gt;
  &lt;/body&gt;
  
  &lt;/html&gt;
  </code></pre>
  
  <p><a href="http://jsfiddle.net/dNm9V/1/" rel="nofollow"><strong>JSFiddle</strong></a></p>