1

When I call app.update() in console this works, but when I use requestAnimationFrame there is an error:

Uncaught TypeError: Cannot read property 'drops' of undefined at update (oee.html:40)

It works while using setInterval.

What am I missing?

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style media="screen">
      *{
        margin:0;
        padding:0;
      }
      #app{
        height:100vh;
        width:100vw;
        background-color: red;
        overflow: hidden;
      }
      .drop{
        position:absolute;
        background-color:#fff;
        top:10px;
        width: 5px;
      }
    </style>
  </head>
  <body>
    <div id="app">
    </div>
    <script type="text/javascript">
      class App{
        constructor(){
          this.el = document.getElementById("app");
          this.height=this.el.clientHeight;
          this.width=this.el.clientWidth;
          this.drops=[];
          for(var i=0;i<100;i++){
            this.drops.push(new Drop(this.height,this.width));
          }
        }
        update(){
          this.drops.forEach(function(drop){
            drop.update();
          });
        }
      }

      class Drop{
        constructor(appHeight,appWidth){
          this.speed=Math.random();
          this.el=document.createElement("div");
          this.el.setAttribute("class","drop");
          this.el.style.height=(Math.random()*10+5)+"px";
          this.el.style.left=(Math.random()*appWidth)+"px";
          this.appHeight=appHeight;
          document.getElementById("app").appendChild(this.el);
          this.el.style.top=0;
        }

        update(){
          this.top=this.el.style.top.replace("px","");
          this.el.style.top=(this.top>this.appHeight)?"0px":(parseFloat(this.top) + parseFloat(this.speed*300))+"px";
        }
      }
      var app=new App();
      requestAnimationFrame(app.update);
    </script>
  </body>
</html>
Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
Shirish Maharjan
  • 502
  • 3
  • 17
  • Possible duplicate of [requestAnimationFrame with this keyword](https://stackoverflow.com/questions/6065169/requestanimationframe-with-this-keyword) | `this` changes when using requestAnimationFrame, so you have to `.bind()` the function like this: https://codepen.io/anon/pen/QgVxxK – yuriy636 Jul 07 '17 at 15:41

1 Answers1

1

When you call app.update(), you are calling the update function as a method of the app object.

But the code shown in the question doesn't call your function as a method:

requestAnimationFrame(app.update);

When you write app.update (without using parentheses), you are just getting a reference to the app.update function as a standalone function. It loses the connection to the app object. When requestAnimationFrame() later calls your update function, it just calls the function directly without any this object. So when update references this.drops it fails.

You didn't list your setInterval() version, but one thing we can be sure of is that it did not look like this:

setInterval( app.update, 10 );

If that were the code you used, it would have the same problem you're having with requestAnimationFrame(). You may have written something like this:

setInterval( function() {
    app.update();
}, 10 );

Now your setInterval() callback is the anonymous function, and that function calls app.update() as a method.

You can do the same thing with requestAnimationFrame(), except that you need to call requestAnimationFrame() each time you want to wait for another frame. In other words, it's more like setTimeout() rather than setInterval(). So it could look like this:

var updater = function() {
    app.update();
    requestAnimationFrame( updater );  // for subsequent frames
};

requestAnimationFrame( updater );  // for the first frame

Now your code should work as expected.

Here's an updated version of yuriy636's CodePen test with animation working.

Michael Geary
  • 28,450
  • 9
  • 65
  • 75