javascripthtmlcanvasputimagedata

putImageData on a loop


I'm trying to build a beautiful graphic image (animated!), calculating a color for each pixel on a screen and then showing all the pixels at once. So, I'm using a putImageData function on a loop.

But for some reason it doesn't work. Only the last iteration can be seen on a screen (after 1-2 sec).

I've tried to use FillRect after each iteration, but it didn't help.

<html>
<head>
    <title>Full Screen Canvas Test</title>
    <style>
        html, body {
            overflow: hidden;
            margin: 0;
        }
    </style>
</head>
<body>

    <canvas id="mainCanvas"></canvas>

    <script>
        (function () {
            canvas = document.getElementById('mainCanvas');
            ctx = canvas.getContext("2d");              
            canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
            canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
            step = 1;

            function doit() {   
                rx = Math.floor(Math.random()*canvas.width);
                ry = Math.floor(Math.random()*canvas.height);
                gx = Math.floor(Math.random()*canvas.width);
                gy = Math.floor(Math.random()*canvas.height);
                bx = Math.floor(Math.random()*canvas.width);
                by = Math.floor(Math.random()*canvas.height);                                               

                var canvasData = ctx.createImageData(canvas.width, canvas.height);

                for (var i = 0; i < canvas.width; i+=step) {
                    for (var j = 0; j < canvas.height; j+=step) {
                        po = 2;
                        rd = Math.floor(Math.sqrt(Math.pow(rx - i, po)+Math.pow(ry - j, po)));
                        gd = Math.floor(Math.sqrt(Math.pow(gx - i, po)+Math.pow(gy - j, po)));
                        bd = Math.floor(Math.sqrt(Math.pow(bx - i, po)+Math.pow(by - j, po)));
                        maxd = 1400;
                        r = Math.floor((1 - rd/maxd)*255);
                        g = Math.floor((1 - gd/maxd)*255);
                        b = Math.floor((1 - bd/maxd)*255);

                        var index = (i + j * canvas.width) * 4;

                        canvasData.data[index + 0] = r;
                        canvasData.data[index + 1] = g;
                        canvasData.data[index + 2] = b;
                        canvasData.data[index + 3] = 255;


                    }
                }

                ctx.putImageData(canvasData, 0, 0);

            }       

            doit();
            doit();
            doit();
            doit();
            doit();
            doit();
            doit();
            doit();
        })();
    </script>
</body>


Solution

  • You need to use setInveral() to call your doit() function multiple times with a time delay between each call. The issue with your current method is that it is running all your doit() results in one iteration, and then displaying the last one only as it overwrites your previous results. Thus, you need to display the results of each sequentially.

    See working example below:

    <html>
    
    <head>
      <title>Full Screen Canvas Test</title>
      <style>
        html,
        body {
          overflow: hidden;
          margin: 0;
        }
      </style>
    </head>
    
    <body>
    
      <canvas id="mainCanvas"></canvas>
    
      <script>
        (function() {
          canvas = document.getElementById('mainCanvas');
          ctx = canvas.getContext("2d");
          canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
          canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
          step = 1;
    
          function doit() {
            rx = Math.floor(Math.random() * canvas.width);
            ry = Math.floor(Math.random() * canvas.height);
            gx = Math.floor(Math.random() * canvas.width);
            gy = Math.floor(Math.random() * canvas.height);
            bx = Math.floor(Math.random() * canvas.width);
            by = Math.floor(Math.random() * canvas.height);
    
            var canvasData = ctx.createImageData(canvas.width, canvas.height);
    
            for (var i = 0; i < canvas.width; i += step) {
              for (var j = 0; j < canvas.height; j += step) {
                po = 2;
                rd = Math.floor(Math.sqrt(Math.pow(rx - i, po) + Math.pow(ry - j, po)));
                gd = Math.floor(Math.sqrt(Math.pow(gx - i, po) + Math.pow(gy - j, po)));
                bd = Math.floor(Math.sqrt(Math.pow(bx - i, po) + Math.pow(by - j, po)));
                maxd = 1400;
                r = Math.floor((1 - rd / maxd) * 255);
                g = Math.floor((1 - gd / maxd) * 255);
                b = Math.floor((1 - bd / maxd) * 255);
    
                var index = (i + j * canvas.width) * 4;
    
                canvasData.data[index + 0] = r;
                canvasData.data[index + 1] = g;
                canvasData.data[index + 2] = b;
                canvasData.data[index + 3] = 255;
    
              }
            }
    
            ctx.putImageData(canvasData, 0, 0);
          }
          
          // Use setInterval to display the images sequentially (every 1000 m/s (every second))
          let showImg = setInterval(doit, 1000);
        })();
      </script>
    </body>