HTML canvas drawImage()方法在第一次加载时不加载切片地图(多个图像的网格)

时间:2019-03-17 18:58:10

标签: javascript html5 canvas html5-canvas drawimage

这与创建图块地图(64x64像素PNG)有关。

出于演示目的,我简化了代码,并将网格缩小为3x3字段。

问题本质上是,当我绘制网格时,图像仍在加载,因此画布在第一次加载时不会显示。但是,刷新后,网格将正常加载,因为现在已缓存所有图像文件。

<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>

可以找到类似的问题:

Canvas image not displaying until second attempt

Why do my canvases only load on refresh?

在这种情况下,尽管我尝试生成的不仅是一张图像,而且还生成了一张图像网格,但由于一点都没有加载,我试图解决这个问题似乎遇到了问题:

<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>

如您所见,我的3个开关选项现在都在等待图像加载,然后再绘制。

我在某处读到它可能与FOR循环有关,我不得不使用IIFE,我在这里可以找到更多信息: What is the (function() { } )() construct in JavaScript? 但是,我完全不熟悉这个术语/概念,也不知道自己是否走对了。

我还试图通过外部js加载,然后在body标签中加载onload =“ drawTileMap()”,但是这甚至引发了更时髦的行为,因为那样的话,它实际上似乎是加载随机图块而不是整个网格

任何帮助,不胜感激!

1 个答案:

答案 0 :(得分:0)

我不建议您沿第二个代码块尝试的路线。将资源加载例程和画布渲染功能分开(如您在第一个代码示例中尝试的那样),以实现更好的维护。 JavaScript是一种基于异步事件的语言,请充分利用它。

我已将您的代码分为两个函数。一种检查是否加载了所有图像,然后在这种情况下调用第二个函数(作为参数传递给它)。

如果您感到舒适并理解了我的示例的工作原理,则应该阅读JavaScript Promises(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),因为它们是处理回调的异步操作的更通用,更好的方法。

下面的示例代码:

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>