Question

First thing, excuse my absolute lack of knowledge in JavaScript. I'm looking for the best approach for this problem, but after 3 days I think it may be wrong.

I need to write some code to draw moving rectangles in different rows in a canvas. In the future I will need to detect when 2 rectangles are in the same X coordinate, so it's important to keep track of the X values. Coming from Java I thought the best would be to create some rectangle "objects" and with each instance a draw method.

What is causing me trouble is that I thought about calling the draw function with setInterval(), but it appears that every time the function draw is called, the values are not the same.

This is my definition of the Rectangle class:

function Rectangle(x,y,width,height) {
      var x=x;
      var y= y;
      var width= width;
      var height= height;

      this.getX = function(){
            return x;
      }
      this.setX = function (value) {
            x = value;
      }

      this.getY = function(){
        return y;
      }

      this.setY = function (value) {
        y = value;
      }

      this.getWidth = function(){
        return width;
      }

      this.setWidth = function (value) {
        width = value;
      }

      this.getHeight = function(){
            return height;
      }

      this.setHeight = function (value) {
            height = value;
      }

      this.draw = function(){

  if(this.getX() <=canvas.width){
        clearContext(this.getX() - 30,this.getY(),this.getWidth(),this.getHeight());    
        var temp= this.getX()+1;
        this.setX(temp);
        ctx.fillRect(temp,this.getY(),this.getWidth(),this.getHeight());
    }else{
          clearInterval(this.draw(),speed);
    }

 } 
}

Then I have a function formSubmit where I create the Rectangles instances when the button is pressed and call respectively the function draw with setInterval():

function formSubmit(){
    number=parseInt(document.getElementById("nummerT").value);
    rate=parseInt(document.getElementById("rate").value);
    speed=parseInt(document.getElementById("speed").value);
    confirm(speed); 
    myRectangle= new Rectangle(0,0,30,30);
    myRectangle2 = new Rectangle(0,60,30,30);
    setInterval(myRectangle.draw(),speed);  
}
Was it helpful?

Solution 3

I have not so much to add to the other answers (+1 to both) but just a note on this part:

function Rectangle(x,y,width,height) {
      var x=x;
      var y= y;
      var width= width;
      var height= height;
       ...

When you do Rectangle(x, y, ...) the compiler/parser will actually do this for you (or rather, the equivalent of):

var x = arguments[0];  // x declared internally, no need to manually declare it
var y = arguments[1];  // y declared too, etc.
...

so you do not need to declare the variables in the function signature as they are already declared - or just leave the signature without any parameters and do the assigning manually (a tad slower but fully legal).

function Rectangle() {
      var x = arguments[0];  // legal but not recommended (in most cases)
      var y = arguments[1];
      ...

So, in conclusion - the recommended approach in this case would be:

function Rectangle(x,y,width,height) {
    // no x,y, width and height decl. here - they're declared by signature
    this.getX = function(){
        return x;
    }
    ...

Second issue: setInterval will need a reference to a function. As it is now the function will invoked due to placing the to parenthesis at the end and the result of that function will be handed as a reference instead.

You can call it like:

setInterval(myRectangle.draw, speed); // only a reference, no parenthesis

But in order to enable cancelling of it you need to store the timer ID:

var timerID;  // global scope

...
timerID = setInterval(myRectangle.draw, speed);

Then use that request to cancel it later:

clearInterval(timerID);

I would too recommend using requestAnimationFrame as this is optimized for animation and monitor sync.

Contrary to setInterval you'll need to call it per frame inside your animation loop. You can use a flag/condition to not call it again when you want to end the animation.

You have also a clearContext method in there - I assume you have that defined elsewhere in the code, if not, check out context.clearRect().

OTHER TIPS

The problem is that setInterval(myRectangle.draw(),speed); doesn't do what you think it does. You are calling draw one time, and then the interval is calling the result of draw. You'll need something like:

interval = setInterval(function() {
  myRectangle.draw();
}, speed);

You'll note, I set the return value of setInterval to a variable because that is how you'll clear the interval later. You just call

clearInterval(interval);

I don't know if that's going to solve all your problems, but it should at least you get to something that will give you some more information.

A Demo: http://jsfiddle.net/m1erickson/SdPPa/

enter image description here

Your instinct of creating rectangle objects to define what is drawn on the canvas is indeed the common standard.

Unlike Java, JavaScript does not have true classes, but you can create a pseudo-class as you have done in your question.

At it's simplest a Rectangle "class" needs these properties:

  • x, y
  • width, height

If you want to animate those rectangles on the canvas you might add:

  • velocityX, directionY
  • velocityY, direction

These new properties allow you to move the rectangles like this:

this.x += this.directionX * this.velocityX;

this.y += this.directionY * this.velocityY;

Hint: Html5 now has an excellent animation handler: requestAnimationFrame. You might want to use this instead of setInterval or setTimeout because it gives better performance by integrating itself with the refresh cycle of the browser.

Hint: JavaScript is a prototypal language so you can extend your "class" with methods. The best way to add methods to a "class" is to add them to the classes prototype. That way the methods are created once and shared by all instances of the class rather than having every method recreated on every instance.

So a method to allow a rectangle instance to draw itself to the canvas might look like this:

// draw this rect on the canvas
Rectangle.prototype.render=function(){
    ctx.fillStyle=this.color;
    ctx.fillRect(this.x,this.y,this.width,this.height);
    return(this);
}

Hint: JavaScript "class" methods can be chained if you always return(this). A good use of chaining might be calling a move method on an instance and then chaining on the render method.

rectangle1.move().render();

There's lots to learn about javascript "classes".

Here's annotated code to start with:

Good luck with your project!

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // canvas related variables
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    // an array to hold all rectangle objects
    var rectangles=[];

    // a rectangle pseudo-class (javascript does not have actual classes)
    function Rectangle(stdProperties) {

        addProperties(this,stdProperties);

        this.color=randomColor();


    };
    //
    // Add methods that apply to all instance rectangles
    // to Rectangle.prototype so those methods are
    // created once for all instances instead of 
    // repeatedly for every instance.
    //

    // set x,y,width,height of this rectangle
    Rectangle.prototype.init=function(x,y,width,height){
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
        return(this);
    };

    // move this rectangle by its preset delta-x and delta-y
    Rectangle.prototype.move=function(){
        var maxRight=canvas.width-this.width;
        var maxBottom=canvas.height-this.height;
        this.x+=this.directionX*this.velocityX;
        if(this.x<0){ this.x=0; this.directionX*=-1}
        if(this.x>maxRight){ this.x=maxRight; this.directionX*=-1}
        this.y+=this.directionY*this.velocityY;
        if(this.y<0){ this.y=0; this.directionY*=-1}
        if(this.y>maxBottom){ this.y=maxBottom; this.directionY*=-1}
        return(this);
    };

    // draw this rect on the canvas
    Rectangle.prototype.render=function(){
        ctx.fillStyle=this.color;
        ctx.fillRect(this.x,this.y,this.width,this.height);
        return(this);
    }

    // create a new rectangle object from the Rectangle "class"
    function newRect(x,y,width,height){

        // define default properties for Rectangle
        var DefaultRectangleProperties={
            x:0,y:0,width:10,height:10,
            velocityX:1,velocityY:1,directionX:1,directionY:1,
            color:"black",
        }

        // new-up a Rectangle
        var rect = new Rectangle(DefaultRectangleProperties);

        // set the x,y,width,height & draw it on the canvas
        rect.init(x,y,width,height).render();

        // return(this) to allow chaining
        return(rect);
    }

    // TESTING

    // create 5 rectangles with some randomness
    for(var i=0;i<5;i++){
        var rect=newRect(Math.random()*200,Math.random()*200,40,50);
        rect.velocityX=Math.random()*2;
        rect.velocityY=Math.random()*3;
        rectangles.push(rect);
    }

    // animate the rectangles using requestAnimationFrame
    animate();


    // the animation loop
    function animate(t){

        // request another animation frame
        requestAnimationFrame(animate);

        // clear the canvas
        // move all the rectangles by their preset distance
        // redraw all the rectangles
        ctx.clearRect(0,0,canvas.width,canvas.height);
        for(var i=0;i<rectangles.length;i++){
            rectangles[i].move().render();
        }

    }


    ///////////////////////////////////
    // Utilities
    ///////////////////////////////////


    // create getters/setters on the specified object
    // using the supplied properties object
    // 
    function addProperties(object,properties){
        for (var i in properties) {
            (function(i) {
                Object.defineProperty(object, i, {
                    get: function(){ return properties[i]; },
                    set: function(val){ properties[i] = val; }
                })
            })(i);
        }        
    }

    // generate a random color
    function randomColor(){ 
        return('#'+Math.floor(Math.random()*16777215).toString(16));
    }


}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top