开发者

Problem with loading images to several canvases

开发者 https://www.devze.com 2023-02-16 17:06 出处:网络
I have a table of canvas elements that emulates tiled image. I need to populate only those ti开发者_如何学Pythonles that are currently visible in the viewport. Here\'s the code I use

I have a table of canvas elements that emulates tiled image. I need to populate only those ti开发者_如何学Pythonles 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:

var image = new Image();
var context = canvasDisp.getContext("2d");
image.onload = function() {
    context.drawImage(image, 0, 0);
}

What that does is create a closure and assign it to the load event of the image. The closure closes over the context and image variables (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 same context and image — the last ones created by the loop.

It doesn't happen in the each version because in that version, the closure closes over a variable that doesn't change, the one context and image created by the call to the iterator function you're passing into each.

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. var is 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:

var ix, iy, canvasDispJq, canvasDisp, image, context;
for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
        for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
            canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "-    disp");
            canvasDisp = canvasDispJq.get(0);
            image = new Image();
            context = canvasDisp.getContext("2d");
            image.onload = function() {
                context.drawImage(image, 0, 0);
            };
            image.src = self.baseURL + '/' + canvasDispJq.attr("id");
        }
    }
}

Note that all the var statements have moved to the top, because that's how they'll be treated. Regardless of where var appears in a function, it takes effect from the very beginning of the function and only happens once. If the var has an initializer, that becomes an assignment where the var is. So var 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, and x = 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 a for loop; but you do want one after the assignment to the onload handler. 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 onload will be referring to the same context and image variables, and so they'll all see the last ones.

If you want to use the non-each version, you just have to change it slightly so the onload closures close over their own copy of context and image. That looks like this:

var ix, iy, canvasDispJq, canvasDisp, image, context;
for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
        for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
            canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "-    disp");
            canvasDisp = canvasDispJq.get(0);
            image = new Image();
            context = canvasDisp.getContext("2d");
            image.onload = createOnloadHandler(context);
            image.src = self.baseURL + '/' + canvasDispJq.attr("id");
        }
    }
}

function createOnloadHandler(ct, img) {

    return function() {
        context.drawImage(img, 0, 0);
    };
}

Now the closure created by the createOnloadHandler function closes over ct and img, not context and image. Each closure gets its own copy (the one passed into the createOnloadHandler call that created that closure).

0

精彩评论

暂无评论...
验证码 换一张
取 消