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

问题描述 投票:1回答:1

这是关于瓦片地图(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标签中加载on =“drawTileMap()”,但是这会引发更加有趣的行为,因为它实际上似乎加载了随机磁贴而不是整个网格。

任何帮助非常感谢!

javascript html5 canvas html5-canvas drawimage
1个回答
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>
© www.soinside.com 2019 - 2024. All rights reserved.