I have a table of canvas elements that emulates tiled image. I need to populate only those tiles that are currently visible in the viewport. Here’s the code I use
for (var ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
for (var iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
var canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp");
var canvasDisp = canvasDispJq.get(0);
var image = new Image();
var context = canvasDisp.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
}
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
}
};
But the problem is that only the last tile from the range is loaded. The same thing but implemented in this way is working fine:
$("table#canvasTable-" + frameResId + " canvas.display").each(function() {
var canvasDispJq = $(this);
if ((canvasDispJq.position().top + self.tileSize + imagePosition.y) > 0 &&
(canvasDispJq.position().left + self.tileSize + imagePosition.x) > 0 &&
(canvasDispJq.position().top + imagePosition.y) < self.containerHeight &&
(canvasDispJq.position().left + imagePosition.x) < self.containerWidth) {
var canvasDisp = canvasDispJq.get(0);
var image = new Image();
var context = canvasRaw.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
}
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
});
Difference in how the visible range is calculated shouldn’t matter because I checked in Firebug and for both approaches tiles to render are selected correctly and actual tile images are downloaded from server but for first approach only last tile is displayed, while for second one all the tiles are rendered correctly.
Thanks in advance for any suggestions.
The reason for the difference is primarily here:
What that does is create a closure and assign it to the
loadevent of the image. The closure closes over thecontextandimagevariables (and several others). The key thing here is that it has an enduring reference to those variables, not a copy of their values when the function was created. Since the above code is in a loop, all of the functions (closures) that get created will refer to the samecontextandimage— the last ones created by the loop.It doesn’t happen in the
eachversion because in that version, the closure closes over a variable that doesn’t change, the onecontextandimagecreated by the call to the iterator function you’re passing intoeach.More about closures here: Closures are not complicated
Perhaps slightly off-topic, perhaps not: This may have been more clear if your
vars were in a different place.varis sometimes misunderstood. In particular, it’s not the same as variable declarations in some other languages (C, C++, Java, and C# for instance), partially because JavaScript doesn’t have block-level scope (only function-level scope and the global scope).This is how the JavaScript interpreter sees your first version:
Note that all the
varstatements have moved to the top, because that’s how they’ll be treated. Regardless of wherevarappears in a function, it takes effect from the very beginning of the function and only happens once. If thevarhas an initializer, that becomes an assignment where thevaris. Sovar x = 2;is really two completely separate things that are handled at separate times by the interpreter:var x, which happens upon entry to the function before anything else happens, andx = 2;, which happens where it appears in the step-by-step execution of the function’s code.(Also, off-topic: There’s no need for a
;after the closing}of aforloop; but you do want one after the assignment to theonloadhandler. I’ve made both of those mods aboev as well.)Writing the code the way the interpreter will see it, now (to me, anyway) it’s clearer that each of the closures you’re assigning to
onloadwill be referring to the samecontextandimagevariables, and so they’ll all see the last ones.If you want to use the non-
eachversion, you just have to change it slightly so theonloadclosures close over their own copy ofcontextandimage. That looks like this:Now the closure created by the
createOnloadHandlerfunction closes overctandimg, notcontextandimage. Each closure gets its own copy (the one passed into thecreateOnloadHandlercall that created that closure).