1

This is about the creation of a tile map (64x64 pixel PNGs).

For demonstration purposes I simplified my code and reduced the grid to a 3x3 field.

The issue is essentially that the images are still loading when I draw the grid, so the canvas won't show on first load. On refresh however, the grid loads fine as all image files are now cached.

<body>
    <canvas id="canvas" width="200px" height="200px"></canvas>

    <script>
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        // 3X3 GRID
        var mapArray=[
            ["tile1","tile3","tile1"],
            ["tile1","tile1","tile2"],
            ["tile3","tile1","tile2"]
        ];

        var tile1 = new Image();
        var tile2 = new Image();
        var tile3 = new Image();

        tile1.src='../images/test/tile1.png';
        tile2.src='../images/test/tile2.png';
        tile3.src='../images/test/tile3.png';

        var posX=0;
        var posY=0;

            for(var i=0; i < mapArray.length; i++) {
                for(var j=0; j < mapArray[i].length; j++) {

                    switch(mapArray[i][j]) {
                        case "tile1":
                            context.drawImage(tile1, posX, posY, 64, 64);
                            break;
                        case "tile2":
                            context.drawImage(tile2, posX, posY, 64, 64);
                            break;
                        case "tile3":
                            context.drawImage(tile3, posX, posY, 64, 64);
                            break;
                    }
                    posX+=64;
                }
                posX=0;
                posY+=64;
            }
        </script>
</body>

Similar issues can be found:

Canvas image not displaying until second attempt

Why do my canvases only load on refresh?

In this case though I am trying to generate not just one image, but a grid of images and my attempt at solving this seems to run into issues as nothing loads at all:

<body>
    <canvas id="canvas" width="200px" height="200px"></canvas>

    <script>
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        // 3X3 GRID
        var mapArray=[
            ["tile1","tile3","tile1"],
            ["tile1","tile1","tile2"],
            ["tile3","tile1","tile2"]
        ];

        var tile1 = new Image();
        var tile2 = new Image();
        var tile3 = new Image();

        var posX=0;
        var posY=0;

            for(var i=0; i < mapArray.length; i++) {
                for(var j=0; j < mapArray[i].length; j++) {

                    switch(mapArray[i][j]) {
                        case "tile1":
                            //use onload so drawing waits until image file is loaded
                            tile1.onload = function() {
                                //draw image
                                context.drawImage(tile1, posX, posY, 64, 64);
                            };
                            //load image file
                            tile1.src='../images/test/tile1.png';
                            break;
                        case "tile2":
                            tile2.onload = function() {
                                context.drawImage(tile2, posX, posY, 64, 64);
                            };
                            tile2.src='../images/test/tile2.png';
                            break;
                        case "tile3":
                            tile3.onload = function() {
                                context.drawImage(tile3, posX, posY, 64, 64);
                            tile3.src='../images/test/tile3.png';
                            break;
                    }
                    posX+=64;
                }
                posX=0;
                posY+=64;
            }
        </script>
</body>

As you can see, my 3 switch options each are now waiting for the image to load before drawing it.

I read somewhere that it might be to do with the FOR loop, and I'd have to use an IIFE which I found more on here: What is the (function() { } )() construct in JavaScript? However, am completely unfamiliar with this term / concept nor do I know if I'm on the right track.

I was also trying to load this via an external js and then onload="drawTileMap()" in the body tag, however that throws up even funkier behaviour, because then it really seems to load random tiles instead of the entire grid.

Any help much appreciated!

Wout
  • 11
  • 2

1 Answers1

0

I would not recommend going down the route you try in your second code block. Keep your resource loading routines and canvas rendering functionality separated (as you tried to do in your first code example) for better maintenance. JavaScript is an asynchronous event based language, use that to your advantage.

I have separated your code into two functions. One that checks if all images are loaded, and calls the second function (passed to it as an argument) when that is the case.

When you are comfortable and understand how my example works, you should read up on JavaScript Promises ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ) as they are a more general and better way to handle asynchronous operations with callbacks.

Example code below:

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

// 3X3 GRID
var mapArray = [
  ["tile1", "tile3", "tile1"],
  ["tile1", "tile1", "tile2"],
  ["tile3", "tile1", "tile2"]
];

var tile1 = new Image();
var tile2 = new Image();
var tile3 = new Image();
var nrOfImagesLoaded = 0;

// Using base64 datauri's, so the script doesn't depend on external resources
tile1.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAZElEQVR42u3QAQ0AAAjDMO5fNGCD0GUKmq7a/xYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgfgMNmH/BjHDEMQAAAABJRU5ErkJggg==';
tile2.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAY0lEQVR42u3QAQ0AAAjDMO5fNGCD0M1BU70/LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMD9Bs3Jf8EvyPO9AAAAAElFTkSuQmCC';
tile3.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAYklEQVR42u3QAQ0AAAgDoL9/aM3hhAg0mcljFSBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAu5bjgl/wXGcqdoAAAAASUVORK5CYII=';

tile1.onload = function() {nrOfImagesLoaded++;}
tile2.onload = function() {nrOfImagesLoaded++;}
tile3.onload = function() {nrOfImagesLoaded++;}

// Function that takes another function as an argument and calls it when 
// all 3 images have been loaded
function callWhenImagesLoaded(callback) {
  // Call the function when all 3 tiles have been loaded
  if(nrOfImagesLoaded == 3) {
    callback();
  // Otherwise poll again in 500 milliseconds to see if they are loaded
  } else {
    setTimeout(function() { callWhenImagesLoaded(callback) }, 500);
  }
}

// Call this, when all images are loaded
function allImagesAreNowLoaded() {
  var posX = 0;
  var posY = 0;

  for (var i = 0; i < mapArray.length; i++) {
    for (var j = 0; j < mapArray[i].length; j++) {

      switch (mapArray[i][j]) {
        case "tile1":
          context.drawImage(tile1, posX, posY, 64, 64);
          break;
        case "tile2":
          context.drawImage(tile2, posX, posY, 64, 64);
          break;
        case "tile3":
          context.drawImage(tile3, posX, posY, 64, 64);
          break;
      }
      posX += 64;
    }
    posX = 0;
    posY += 64;
  }
}

// Tie it all togther - call allImagesAreNowLoaded when all 3 tiles have been loaded
callWhenImagesLoaded(allImagesAreNowLoaded);
<canvas id="canvas" width="200px" height="200px"></canvas>
Pärt Johanson
  • 1,570
  • 1
  • 13
  • 13