Alex K
Alex K

Reputation: 43

HTML Canvas shape animation stuck at end of window after resize

I am working on a fun intro project to HTML Canvas. The basic concept is that some shapes in this case circles move around and bounce off the sides. The two problems I would like help with are:

  1. After resizing the browser the circles may get stuck at the end of the screen on the open sides(bottom and right) where they are bounded by widthCanvas and heightCanvas. Ideally, I would the circles to behave the same way as they do on the sides(left and top) where they are bounded by 0. (See the if statements in the animate() function)

  2. How to remove the white space around the canvas left due to the use of:

    canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        html,
        body {
            overflow: hidden;
        }
    </style>
</head>

<body onresize="resizeCanvas()">
    <div>
        <canvas id="canvas"></canvas>
    </div>

    <script>

        const canvas = document.getElementById('canvas');
        /** @type {CanvasRenderingContext2D} */
        const ctx = canvas.getContext('2d');

        canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
        canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
        widthCanvas = canvas.width;
        heightCanvas = canvas.height;

        draw(40);

        function draw(num) {
            //number of cycles 
            num_cycles = num;

            // speed bounds 
            min_speed = 4;
            // real max_speed is + min_speed
            max_speed = 3;

            // create arrays for changing values 
            const arr_x = new Array(num_cycles);
            const arr_y = new Array(num_cycles);
            const arr_dx = new Array(num_cycles);
            const arr_dy = new Array(num_cycles);
            const arr_r = new Array(num_cycles);

            // populate arrays with random vaalues in range so circles 
            // have different velocities on y and x as well as vary in radii
            for (let index = 0; index < num_cycles; index++) {
                arr_x[index] = ((widthCanvas * 0.2) * Math.random()) + (widthCanvas * 0.4);
                arr_y[index] = ((heightCanvas * 0.2) * Math.random()) + (heightCanvas * 0.4);
                arr_dx[index] = (min_speed + Math.random() * max_speed) * (Math.round(Math.random()) ? -1 : 1);
                arr_dy[index] = (min_speed + Math.random() * max_speed) * (Math.round(Math.random()) ? 1 : -1);
                arr_r[index] = 50 + Math.random() * widthCanvas / 12;
            }

            let arr_color = ["rgba(47, 133, 209, 0.6)", "rgba(244, 67, 54, 0.6)", "rgba(71, 50, 123, 0.6)", "rgba(241, 194, 50, 0.6)", "rgba(56, 118, 29, 0.6)"];

            function animate() {

                //clear the canvas so that new drawings are on a blank canvas 
                ctx.clearRect(0, 0, widthCanvas, heightCanvas)
                ctx.fillStyle = "rgba(224, 31, 92, 1)";
                ctx.fillRect(0, 0, widthCanvas, heightCanvas)

                // Draw shapes aand animations   

                for (var i = 0; i <= widthCanvas; i += widthCanvas / 120) {
                    addLinesPath(i, i, 0, heightCanvas, "rgba(31, 118, 224, 0.5)");
                }

                for (let index = 0; index < num_cycles; index++) {

                    draw_circle(arr_x[index], arr_y[index], arr_r[index], arr_color[index % arr_color.length])

                    if (arr_x[index] + arr_r[index] > widthCanvas || arr_x[index] - arr_r[index] < 0)
                        arr_dx[index] = -arr_dx[index];

                    if (arr_y[index] + arr_r[index] > heightCanvas || arr_y[index] - arr_r[index] < 0)
                        arr_dy[index] = -arr_dy[index];

                    arr_x[index] += arr_dx[index];
                    arr_y[index] += arr_dy[index];
                }

                requestAnimationFrame(animate);
            }

            animate()

        }

        // resize
        function resizeCanvas() {
            canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
            canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

            widthCanvas = canvas.width;
            heightCanvas = canvas.height;
        }

        function get_width() {
            return canvas.width;
        }
        function get_height() {
            return canvas.height;
        }

        // draw line
        function addLinesPath(y_start, y_end, x_start, x_end, color) {
            ctx.strokeStyle = color;
            ctx.lineWidth = 3;
            ctx.beginPath();
            ctx.moveTo(y_start, x_start);
            ctx.lineTo(y_end, x_end);
            ctx.stroke();
        }

        // draw circle
        function draw_circle(x, y, r, color) {
            ctx.fillStyle = color;
            ctx.beginPath();
            ctx.arc(x, y, r, 0, 2 * Math.PI);
            ctx.fill();
        }

    </script>
</body>

</html>

Upvotes: 1

Views: 70

Answers (1)

the Hutt
the Hutt

Reputation: 18428

After resizing the browser the circles may get stuck...

Consider following simplified code from your snippet:

if (arr_x[index] + arr_r[index] > widthCanvas)
   arr_dx[index] = -arr_dx[index];

Here, when the circle goes out of right border due to resizing, the if statement evaluates to true all the time. Because you are switching direction every frame! 😬
Make sure you switch the direction only once after the circle goes out of the canvas.



How to remove the white space around the canvas left...

By default <body> has some margin. In Chrome 8px. Need to check other browser. Get rid of those margins and paddings if any on all the involved elements. Or adjust the canvas width in code accordingly.


Demo:

<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html,
    body {
      overflow: hidden;
    }
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
  </style>
</head>

<body onresize="resizeCanvas()">
  <div>
    <canvas id="canvas"></canvas>
  </div>

  <script>
    const canvas = document.getElementById('canvas');
    /** @type {CanvasRenderingContext2D} */
    const ctx = canvas.getContext('2d');

    canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    widthCanvas = canvas.width;
    heightCanvas = canvas.height;

    draw(10);

    function draw(num) {
      //number of cycles 
      num_cycles = num;

      // speed bounds 
      min_speed = 2;
      // real max_speed is + min_speed
      max_speed = 2;

      // create arrays for changing values 
      const arr_x = new Array(num_cycles);
      const arr_y = new Array(num_cycles);
      const arr_dx = new Array(num_cycles);
      const arr_dy = new Array(num_cycles);
      const arr_r = new Array(num_cycles);

      // populate arrays with random vaalues in range so circles 
      // have different velocities on y and x as well as vary in radii
      for (let index = 0; index < num_cycles; index++) {
        arr_x[index] = ((widthCanvas * 0.2) * Math.random()) + (widthCanvas * 0.4);
        arr_y[index] = ((heightCanvas * 0.2) * Math.random()) + (heightCanvas * 0.4);
        arr_dx[index] = (min_speed + Math.random() * max_speed) * (Math.round(Math.random()) ? -1 : 1);
        arr_dy[index] = (min_speed + Math.random() * max_speed) * (Math.round(Math.random()) ? 1 : -1);
        arr_r[index] = 50 + Math.random() * widthCanvas / 12;
      }

      let arr_color = ["rgba(47, 133, 209, 0.6)", "rgba(244, 67, 54, 0.6)", "rgba(71, 50, 123, 0.6)", "rgba(241, 194, 50, 0.6)", "rgba(56, 118, 29, 0.6)"];

      function animate() {

        //clear the canvas so that new drawings are on a blank canvas 
        ctx.clearRect(0, 0, widthCanvas, heightCanvas)
        ctx.fillStyle = "rgba(224, 31, 92, 1)";
        ctx.fillRect(0, 0, widthCanvas, heightCanvas)

        // Draw shapes and animations   

        for (var i = 0; i <= widthCanvas; i += widthCanvas / 120) {
          addLinesPath(i, i, 0, heightCanvas, "rgba(31, 118, 224, 0.5)");
        }

        for (let index = 0; index < num_cycles; index++) {

          draw_circle(arr_x[index], arr_y[index], arr_r[index], arr_color[index % arr_color.length])

          if (arr_x[index] + arr_r[index] > widthCanvas && arr_dx[index] > 0)
            arr_dx[index] *= -1;
          else if (arr_x[index] - arr_r[index] < 0 && arr_dx[index] < 0)
            arr_dx[index] *= -1;

          if (arr_y[index] + arr_r[index] > heightCanvas && arr_dy[index] > 0)
            arr_dy[index] *= -1;
          else if (arr_y[index] - arr_r[index] < 0 && arr_dy[index] < 0)
            arr_dy[index] *= -1;

          arr_x[index] += arr_dx[index];
          arr_y[index] += arr_dy[index];
        }

        requestAnimationFrame(animate);
      }

      animate()

    }

    // resize
    function resizeCanvas() {
      canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
      canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

      widthCanvas = canvas.width;
      heightCanvas = canvas.height;
      console.log(widthCanvas, '=', heightCanvas);
    }

    function get_width() {
      return canvas.width;
    }

    function get_height() {
      return canvas.height;
    }

    // draw line
    function addLinesPath(y_start, y_end, x_start, x_end, color) {
      ctx.strokeStyle = color;
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(y_start, x_start);
      ctx.lineTo(y_end, x_end);
      ctx.stroke();
    }

    // draw circle
    function draw_circle(x, y, r, color) {
      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.arc(x, y, r, 0, 2 * Math.PI);
      ctx.fill();
    }
  </script>
</body>

</html>

Here, to avoid complex code I've separated the if statements.


With simple debugging we can easily figure out such pesky bugs. This guide may help: https://developer.chrome.com/docs/devtools/javascript/

Upvotes: 1

Related Questions