Reputation: 161
I am trying to draw multiple images onto an HTML Canvas element.
assetData
is an array of objects, each containing the data required to write an image to the canvas, fetched from a database. Crucially, assetData
could be of any length: It could contain one image, or it could contain 100.
The code below does not work, as gfxCtx.drawImage()
draws the final image specified in the array multiple times, rather than each individual image.
for(var x = 0; x < assetData.length; x++) {
var layer = eval(assetData[x])
var gfx = new Image
gfx.src = "images/" + layer.src
gfx.onload = function() {
gfxCtx.drawImage(gfx, layer.x, layer.y, layer.w, layer.h)
compile()
}
}
compile()
handles drawing the images onto the canvas
If there were a fixed number of images to be drawn, I would assign each to a separate variable. In this instance, however, I do not necessarily know how many images are to be drawn, and I would like to keep the code fairly concise.
Is there a clean, straightforward solution to this problem?
Thanks!
Upvotes: 2
Views: 568
Reputation: 19376
This is a FAQ about the effects of asynchronous code. When an image onload
handler is called back later, it sees the value of layer
and gfx
set in the last loop iteration. This results in the last image being drawn multiple times into the last layer's canvas area. (I would be surprised if errors were not sometimes generated on the console as well.)
The simplest solution is to declare the layer
and gfx
variables using let
. This makes them block scoped and creates a separate binding for each loop iteration: the callback sees the variable values for the iteration in which the call back function object was created.
For extended discussion of the topic please refer to JavaScript closure inside loops – simple practical example
Upvotes: 1
Reputation: 30390
To resolve this issue, consider using the forEach()
method on your assetData
array, rather than a regular for
loop as you currently are.
This has to advantage of creating a unique "closure" for each loop iteration, which in turn allows you to correctly draw each unique image into the canvas as required:
assetData.forEach(function(assetItem) {
// This function has a unique closure which
// allows you to draw the current assetItem
// to the canvas to achieve the desired result
var layer = eval(assetItem);
var gfx = new Image();
gfx.src = "images/" + layer.src;
gfx.onload = function() {
gfxCtx.drawImage(gfx, layer.x, layer.y, layer.w, layer.h);
compile();
}
});
Upvotes: 2
Reputation: 1086
Try referencing the onload event target instead of gfx
because the value of gfx
may have changed by the time the image loads.
for(var x = 0; x < assetData.length; x++) {
var layer = eval(assetData[x])
var gfx = new Image
gfx.src = "images/" + layer.src
gfx.onload = function(e) {
gfxCtx.drawImage(e.target, layer.x, layer.y, layer.w, layer.h)
compile()
}
}
Upvotes: 0