Search code examples
javascriptanimationcanvaspositionrectangles

JavasScript How to stop animation if 2 object hits each other


I made a script about moving rectangles. I'd like to stop the animation when the two object hits each other and make a javasript output of bot of the rectangles to te top left. How can I do that?

  • how can I make an output to the top left of the current positions from both rectangles?

Here is my actual code:

<!DOCTYPE html>
<html>
<head>
<script type='text/javascript'>
window.onload=function(){
    var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

// define a rect using a javascript object
var rect1={
  x:25,
  y:150,
  width:180,
  height:50,
  directionX:1
}

// define another rect using a javascript object
var rect2={
  x:800,
  y:150,
  width:200,
  height:80,
  directionX:-1
}

// put each rect in a rects[] array
var rects=[rect1,rect2];

// start the animation loop
requestAnimationFrame(animate);

function animate(time){

  // move each rect in the rects[] array by its 
  // own directionx
  for(var i=0;i<rects.length;i++){
    rects[i].x+=rects[i].directionX;
  }

  // draw all the rects in their new positions
  draw();

  // request another frame in the animation loop
  requestAnimationFrame(animate);
}

function draw(){
  ctx.clearRect(0,0,cw,ch);
  for(var i=0;i<rects.length;i++){
    var r=rects[i]
    ctx.strokeRect(r.x,r.y,r.width,r.height);  
  }

}


}
</script>
</head>
<body>
  <canvas id="canvas" width="1000px" height="600"
    style="border: 1px solid #000000;"></canvas>
</body>
</html>

Solution

  • You run the animation by calling requestAnimationFrame(animate)

    So if you wanna stop the animation, it can be simple, just stop calling it ! :)

    How ?

    You might use an if statement :

    if(collision){
        // Not requesting a new frame
        // Do the actions on collision
    } else {
        // Requesting a new frame
        requestAnimationFrame(animate);
    }
    

    The collision function will look like this:

    function collision(){
        let collision = false;
        if(rect1.x > rect2.x + rect2.width ||
           rect1.x + rect1.width < rect2.x ||
           rect1.y > rect2.y + rect2.height ||
           rect1.y + rect1.height < rect2.y){
            // console.log(`No collision between rect1 and rect2`);
        } else {
            // console.log(`Collision !! between rect1 and rect2`);
            collision = true;
        }
        return collision;
    }
    

    Explanation of the no-collision conditions :

    enter image description here enter image description here enter image description here enter image description here

    For what is about the output, you can use fillText() to log the positions of your rectangles where you want on the canvas.

    for(let i = 0; i < rects.length; i++){
        ctx.font = `20px Arial`;
        ctx.fillStyle = "black";
        ctx.textAlign = "left";
        ctx.textBaseline = "top";
        ctx.fillText(`Rect ${i + 1}: x = ${rects[i].x}, y = ${rects[i].y}`, 10, 10 + 20 * i);
    }
    

    For multiple rectangles :

    window.onload = function(){
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      const cw = canvas.width;
      const ch = canvas.height;
      
      const rect1 = {
        x: 25,
        y: 30,
        width: 180,
        height: 50,
        directionX: 1
      }
      
      const rect2 = {
        x: 800,
        y: 30,
        width: 200,
        height: 80,
        directionX: -1
      }
      
      const rect3 = {
        x: -215,
        y: 90,
        width: 500,
        height: 90,
        directionX: 1
      }
      
      const rects = [rect1, rect2, rect3];
      
      function collision(){
        let collision = false;
        for(let i = 0; i < rects.length - 1; i++){
          for(let j = i + 1; j < rects.length; j++){
            if( rects[i].x > rects[j].x + rects[j].width ||
                rects[i].x + rects[i].width < rects[j].x ||
                rects[i].y > rects[j].y + rects[j].height ||
                rects[i].y + rects[i].height < rects[j].y){
              // console.log(`No collision between rect${i+1} and rect${j+1}`);
            } else {
              // console.log(`Collision !! between rect${i+1} and rect${j+1}`);
              collision = true;
            }
          }
        }
        return collision;
      }
      
      function logPosition(){
        for(let i = 0; i < rects.length; i++){
          ctx.font = `14px Arial`;
          ctx.fillStyle = "black";
          ctx.textAlign = "left";
          ctx.textBaseline = "top";
          ctx.fillText(`Rect ${i + 1}: x = ${rects[i].x}, y = ${rects[i].y}`, 10, 10 + 14 * i);
        }
      }
      
      function update(){
        for(let i = 0; i < rects.length; i++){
          rects[i].x += rects[i].directionX;
        }
      }
    
      function draw(){
        ctx.clearRect(0, 0, cw, ch);
        for(let i = 0; i < rects.length; i++){
          // ------ Draw the rectangle --------------
          ctx.strokeRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
          
          // ------ Show name on the rectangle ------
          ctx.font = `20px Arial`;
          ctx.textAlign = "center";
          ctx.textBaseline = "middle";
          ctx.fillStyle = "black";
          ctx.fillText(`Rect ${i + 1}`, rects[i].x + rects[i].width / 2, rects[i].y + rects[i].height / 2);
          // ----------------------------------------
        }
      }
    
      function animate(time){
      
        // Update rectangles position
        update();
        
        // Draw updated rectangles
        draw();
        
        // Outpout (top-left) the position of the rectangles if collision otherwise, request a new frame
        if(collision()){
          logPosition();
          console.log("COLLISION");
        } else {
          requestAnimationFrame(animate);
        }
      }
    
      // Start the animation
      requestAnimationFrame(animate);
    }
    <!DOCTYPE html>
    <html>
    <head>
    
    </head>
    <body>
      <canvas id="canvas" width="1000px" height="600"
        style="border: 1px solid #000000;"></canvas>
    </body>
    </html>

    Why multiple rectangles ?

    Because as you used an stocking array (rects) & for-loops for only 2 rectangles, I supposed it could be more rectangles in the future, so I went for a more general case. But if it was really intented for only 2 rectangles, I would drop the stocking array & the for-loops.

    For only 2 rectangles :

    window.onload = function(){
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      const cw = canvas.width;
      const ch = canvas.height;
      
      const rect1 = {
        x: 25,
        y: 50,
        width: 180,
        height: 50,
        directionX: 1
      }
      
      const rect2 = {
        x: 800,
        y: 50,
        width: 200,
        height: 80,
        directionX: -1
      }
      
      function collision(){
        let collision = false;
        if( rect1.x > rect2.x + rect2.width ||
            rect1.x + rect1.width < rect2.x ||
            rect1.y > rect2.y + rect2.height ||
            rect1.y + rect1.height < rect2.y){
          // console.log(`No collision between rect1 and rect2`);
        } else {
          // console.log(`Collision !! between rect1 and rect2`);
          collision = true;
        }
        return collision;
      }
      
      function logPosition(){
        ctx.font = `14px Arial`;
        ctx.fillStyle = "black";
        ctx.textAlign = "left";
        ctx.textBaseline = "top";
        ctx.fillText(`Rect 1: x = ${rect1.x}, y = ${rect1.y}`, 10, 10);
        ctx.fillText(`Rect 2: x = ${rect2.x}, y = ${rect2.y}`, 10, 24);
      }
      
      function update(){
        rect1.x += rect1.directionX;
        rect2.x += rect2.directionX;
      }
    
      function draw(){
        ctx.clearRect(0, 0, cw, ch);
        
        // ------ Draw the rectangles ---------------
        ctx.strokeRect(rect1.x, rect1.y, rect1.width, rect1.height);
        ctx.strokeRect(rect2.x, rect2.y, rect2.width, rect2.height);
          
        // ------ Show name on the rectangles ------
        ctx.font = `20px Arial`;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillStyle = "black";
        ctx.fillText(`Rect 1`, rect1.x + rect1.width / 2, rect1.y + rect1.height / 2);
        ctx.fillText(`Rect 2`, rect2.x + rect2.width / 2, rect2.y + rect2.height / 2);
        // -----------------------------------------
      }
    
      function animate(time){
      
        // Update rectangles position
        update();
        
        // Draw updated rectangles
        draw();
        
        // Outpout (top-left) the position of the rectangles if collision otherwise, request a new frame
        if(collision()){
          logPosition();
          console.log("COLLISION");
        } else {
          requestAnimationFrame(animate);
        }
      }
    
      // Start the animation
      requestAnimationFrame(animate);
    }
    <!DOCTYPE html>
    <html>
    <head>
    
    </head>
    <body>
      <canvas id="canvas" width="1000px" height="600"
        style="border: 1px solid #000000;"></canvas>
    </body>
    </html>

    That's it !

    But if you have others actions and that you need the animation loop to keep running, then, the other option is to stop updating the logic (the position of the rectangle). It can be achieved by using an if statement :

    function animate(time){
    
        // Update rectangles position
        if(!collision()){
            update();
        } else {
            logPosition();
            console.log("COLLISION");
        }
        
        // Draw updated rectangles
            draw();
        
        // Outpout (top-left) the position of the rectangles if collision otherwise, request a new frame
        requestAnimationFrame(animate);
    
    }